某CTF代碼審計題


記一次參加CTF比賽翻車記!

  CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

開始還是挺有信心的,畢竟也是經常打一些CTF鍛煉,然而比賽發現大佬們平時不顯山不漏水的一比賽全出來了!賽后看了一下各題的writeup發現自己的確技不如人啊!借鑒一個案例拿出來分析一下!

正言:

 

 CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

這是入口界面登錄+注冊,開始注冊登錄看了一下

 

  CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

有了一個簡單的提你好啊,但是你好像不是XDSEC的人,所以我就不給你flag啦~~ 

然而成功誤導了我,百度了一番找到XDSEC官網,拿XDSEC的隊員名稱注冊了一番,並無卵用!然后爆破目錄、並無任何發現。

思路斷了!之后才知道是由於Phpstorm IDE 開發過程中會生成一個.idea的緩存目錄(里面包含一些敏感文件)輸入url/.idea/workspace.xml 成功下載下來

CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog 

發現一個zip壓縮包(里面是它的源碼無疑)下載后

 CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

 

里面只有三個文件注冊、登錄、用戶下面開始我們的代碼審計

Register.php

<?php
include('config.php');
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';

if (strlen($username) > 16 || strlen($username) > 16) {
die('Invalid input');
}

$sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch() !== false) {
die('username has been registered');
}

$sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
$sth->execute([':username' => $username, ':password' => $password]);

preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
$sth->execute([':username' => $username, ':identity' => $matches[1]]);
} else {
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
$sth->execute([':username' => $username]);
}
echo '<script>alert("register success");location.href="http://ashe666.blog.163.com/blog/./index.html"</script>';

 

Login.php

<?php
session_start();
include('config.php');
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');

if (strlen($username) > 32 || strlen($password) > 32) {
die('Invalid input');
}

$sth = $pdo->prepare('SELECT password FROM users WHERE username = :username');
$sth->execute([':username' => $username]);
if ($sth->fetch()[0] !== $password) {
die('wrong password');
}
$_SESSION['username'] = $username;
unset($_SESSION['is_logined']);
unset($_SESSION['is_guest']);
#echo $username;
header("Location: member.php");
?>

 

Member.php

<?php
error_reporting(0);
session_start();
include('config.php');
if (isset($_SESSION['username']) === false) {
die('please login first');
}
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
$sth->execute([':username' => $_SESSION['username']]);
if ($sth->fetch()[0] === 'GUEST') {
$_SESSION['is_guest'] = true;
}

$_SESSION['is_logined'] = true;
if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {

}else{
if(isset($_GET['file'])===false)
echo "None";
elseif(is_file($_GET['file']))
echo "you cannot give me a file";
else
readfile($_GET['file']);
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body background="./images/1.jpg">
<object type="application/x-shockwave-flash" style="outline:none;" data="http://cdn.abowman.com/widgets/hamster/hamster.swf?" width="300" height="225"><param name="movie" value="http://cdn.abowman.com/widgets/hamster/hamster.swf?"></param><param name="AllowScriptAccess" value="always"></param><param name="wmode" value="opaque"></param></object>
<p style="color:orange">你好啊,但是你好像不是XDSEC的人,所以我就不給你flag啦~~</p>
</body>
</html>

 

直到member.php發現是一個文件讀取漏洞,既然是一個文件讀取漏洞而前面看.idea緩存里面有config.php 這樣也許就可以獲取我們所需要的信息,而漏洞形成是需要條件的下面我們來具體分析一下。

如果你能看懂上面的代碼那么應該已經發現漏洞地點了。

 漏洞觸發地點:

member.php 第28行

<?php
error_reporting(0);
session_start();
include('config.php');
if (isset($_SESSION['username']) === false) {
die('please login first');
}
try{
$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
}catch (Exception $e){
die('mysql connected error');
}
$sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
$sth->execute([':username' => $_SESSION['username']]);
if ($sth->fetch()[0] === 'GUEST') {
$_SESSION['is_guest'] = true;
}
$_SESSION['is_logined'] = true;
if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {

}else{
if(isset($_GET['file'])===false)
echo "None";
elseif(is_file($_GET['file']))
echo "you cannot give me a file";
else
readfile($_GET['file']);
}
?>

 

 

readfile($_GET['file']); 導致文件讀取漏洞,漏洞利用的前提是先達到前面的條件

isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true

 想讓他們執行到else語句,必須繞過isset($_SESSION['is_guest']) === true這個判斷條件

 

 $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

 

 繼續跟蹤到register.php

 

  $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
    $sth->execute([':username' => $username, ':password' => $password]);

    preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
    if (count($matches) === 3 && $admin === $matches[0]) {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
        $sth->execute([':username' => $username, ':identity' => $matches[1]]);
    } else {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
        $sth->execute([':username' => $username]);
    }
    echo '<script>alert("register success");location.href="http://ashe666.blog.163.com/blog/./index.html"</script>';

 

 

 這里進行一個判斷滿足條件

  if (count($matches) === 3 && $admin === $matches[0]) {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
        $sth->execute([':username' => $username, ':identity' => $matches[1]]);
    } 

 

 

 那么我們只需要通過這個判斷就可以繞過isset($_SESSION['is_guest']) === true這個條件

$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {

 

而過程就是這個正則
$code 是可控的匹配字符、$matches是返回值儲存地址

而上面就是需要讓$matches[0]===$admin

而$matches[0]是匹配到的值

$admin的值由於str_shuffle函數是不確定的,當然我們也可以通過爆破來實現,然而我們還有一個更好的方法。

通過$code傳入長字符串來讓preg_match函數消耗資源(拖延時間)導致后面的語句暫時無法執行,而此時我們的賬戶已經注冊成功了,由於傳入大量字符串preg_match不能在短時間內執行完成所以我們可以在這段時間內進行漏洞利用,由於數據庫查詢是空的所以可以繞過驗證。

 漏洞復現:

payload:to=reg&did=0&username=coolbreeze&password=coolbreeze&code=xdsec###超長字符串

注冊處使用burp攔截修改

  CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

 這是點擊Go
進入登錄
直接構造payload:member.php?file=php://filter/resource=config.php
因為前面通過is_file()函數來過濾所以這里通過php偽協議來讀取數據

 

 CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

 

 這里成功獲取CTF

$flag = "LCTF{pr3_maTch_1s_A_amaz1ng_Function}"
到此結束

CTF之翻車記(一) - Coolbreeze - Coolbreezes Blog

 
       


免責聲明!

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



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