php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图1

进入题目是一个登陆框,这没有给我们注册,所以一开始的密码找回漏洞啊,注册漏洞啊。就用不上了。

没有给我们注册,认为是sql注入。

输入

username:admin’#

password:123

回显

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图2

没有报错。那么注入不了。

这时候用扫描后台扫一下下(ps:因为部分网站设置了不能过快过多请求,会返回429,所以要设定时间参数)

那么御剑不可以设定时间参数,定然跑不出来。看来网上的wp发现。可以用dirsearch跑出来,因为他可以设置时间参数

说一下dirsearch的使用方法:https://www.cnblogs.com/SCHAOGES/p/11039037.html

直接拿大佬的wp源码来用吧

首先是index.php

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图3

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图4

在输入账号密码之后进入了profile.php,下面是profile.php的源码:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图5

还有注册页面的源码(没有太大用),register.php:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图6

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图7

然后是update.php:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图8

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图9

核心的处理代码,class.php:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图10

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图11

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图12

最后是config.php:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图13

看来flag就是在config.php中了,要想办法拿到config.php的内容了。

这个地方貌似有个文件读取的地方,在profile.php中:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图14

上面还有个反序列化unserialize,感觉有戏,如果$profile[‘photo’]是config.php就可以读取到了,可以对photo进行操作的地方在update.php,有phone、email、nickname和photo这几个。

登录后注册就不用看了 我们关键看上传资料和显示资料那里

update.php

  1. <?php
  2. require_once('class.php');
  3. if($_SESSION['username'] == null) {
  4. die('Login First');
  5. }
  6. if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
  7. $username = $_SESSION['username'];
  8. if(!preg_match('/^\d{11}$/', $_POST['phone']))
  9. die('Invalid phone');
  10. if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
  11. die('Invalid email');
  12. if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
  13. die('Invalid nickname');
  14. $file = $_FILES['photo'];
  15. if($file['size'] < 5 or $file['size'] > 1000000)
  16. die('Photo size error');
  17. move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
  18. $profile['phone'] = $_POST['phone'];
  19. $profile['email'] = $_POST['email'];
  20. $profile['nickname'] = $_POST['nickname'];
  21. $profile['photo'] = 'upload/' . md5($file['name']);
  22. $user->update_profile($username, serialize($profile));
  23. echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
  24. }
  25. else {
  26. ?>

??一眼可以看出来这里用了一堆的正则表达式来过滤我们提交的数据,而且第三个正则表达式和前面两个不一样,这里判断了nickname是否为字符还有长度是否超过10.用参数传递数组绕过字符串检测,如果我们传入的nickname是一个数组,绕过长度的限制。则可以绕过这正则表达式,使我们不会die出

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图15

??在代码后面调用了update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以大胆的尝试用反序列化漏洞来搞一下

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图16

??我们再看看update_profile()到底是个啥,使用全局搜索我们再class.php中看到了定义的update_profile()方法

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图17

??我们再继续追寻下去

filter()

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图18

update()

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图19

??update.php我们基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为‘hacker‘


—- 然后我们再看profile.php

  1. <?php
  2. require_once('class.php');
  3. if($_SESSION['username'] == null) {
  4. die('Login First');
  5. }
  6. $username = $_SESSION['username'];
  7. $profile=$user->show_profile($username);
  8. if($profile == null) {
  9. header('Location: update.php');
  10. }
  11. else {
  12. $profile = unserialize($profile);
  13. $phone = $profile['phone'];
  14. $email = $profile['email'];
  15. $nickname = $profile['nickname'];
  16. $photo = base64_encode(file_get_contents($profile['photo']));
  17. ?>

??我们这里可以看到有反序列化还有文件读取,我们对这道题有了大致思路了。

flag在config.php中,而且有序列化,过滤替换,反序列化,文件读取,这不就是CTF中反序列化字符串逃逸的常见套路吗。我们构造包含config.php的数据,利用字符串逃逸,在profile.php中读出来

2.3 反序列化字符逃逸知识补充

PHP反序列化字符逃逸
举个小例子

序列化

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图20

结果

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图21

反序列化

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图22

运行结果

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图23

??我们可以看到在后端中,反序列化是一”;}结束的,如果我们把”;}带入需要反序列化的字符串中(除了结尾处),是不是就能让反序列化提前结束后面的内容就丢弃了呢?

??我们把第二个值abc换成abc”;i:2;s:5:”qwert”;}

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图24

运行结果

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图25

成功的反序列化出我们自己定义的内容,丢弃了原先的内容(i:2;s:4:”defg”)
反序列化字符逃逸就先介绍到这里,我们回过头来看一下题

突破口

我们发现一个问题,我们反序列化字符逃逸,首先序列化的字符是可控的,还有前面的长度是可控的。但update.php将参数序列化,我们可控变量的长度就已经写死了,怎么才能去控制呢。这道题的突破口其实就是序列化过后数据过滤替换那里,看似更加安全,其实更加危险。

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图26

这里将’select’,’insert’,’update’,’delete’,’where’替换成’hacker’,我们写入where替换成hacker之后字符串实际长度就+1,因此实际的长度大于序列化固定的长度(变量前面s的值)。利用反序列化字符逃逸,反序列化时只能将字符串中nickname前面的s后面长度的字符串反序列化成功,这个传参的时候就固定好了的。剩下的字符串我们构造成class.php因为里面包含了flag,并且让他在photo位置上,然后把photot给扔掉,这样profile.phph中独缺的photo就是我们构造的config.php了,也就是读到了flag

??简单说就是利用后端的函数替换,导致实际长度增加,增加的部分(config.php)被挤了出来,到了photo的位置上,然后闭合。
??再举个例方便大家理解

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia) - 图27

abc前面s:3:不变,因为序列化的时候固定了

我们将abc构造成: abc”;i:2;s:5:”qwert”;} 我们再最后构造一个闭合,导致defg被丢弃,qwert占用了defg原理的位置

还是回到这一题上,我们的目的是将”;}s:5:”photo”;s:10:”config.php”;}插入序列化的字符串里面去,这个的长度为34,所有我们要挤出来34位,不然就成了nickname的值了.where会替换成hacker,长度+1,所以我们要构造34个where.然后去profile.php查看读取的内容

补充

??之前我传入的是”;s:5:”photo”;s:10:”config.php”;}结果失败了,看了网上的一些文章,发现他们传入的是”;}s:5:”photo”;s:10:”config.php”;}为什么前面要多加一个},后来发现是因为我们nickname构造成了数组,而不是字符,所以要加}闭合一下。