1. 程式人生 > >【Writeup】Boston Key Party CTF 2015(部分題目)

【Writeup】Boston Key Party CTF 2015(部分題目)

假期試著做了一下這場美國的CTF比賽,無奈題目看了一遍都沒什麼想法,只好等比賽結束再學習了。在這裡總結一下學到的姿勢。

(以下是六道php程式碼審計題目)

1.Prudential

  I don‘t think that sha1 is broken. Prove me wrong.

程式碼如下:

<html>
<head>
	<title>level1</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['name']) and isset($_GET['password'])) {
    if ($_GET['name'] == $_GET['password'])
        print 'Your password can not be your name.';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
      die('Flag: '.$flag);
    else
        print '<p class="alert">Invalid password.</p>';
}
?>

<section class="login">
	<div class="title">
		<a href="./index.txt">Level 1</a>
	</div>

	<form method="get">
		<input type="text" required name="name" placeholder="Name"/><br/>
		<input type="text" required name="password" placeholder="Password" /><br/>
		<input type="submit"/>
	</form>
</section>
</body>
</html>

分析程式碼邏輯,發現GET了兩個欄位name和password,獲得flag要求的條件是:name != password & sha1(name) == sha1(password),乍看起來這是不可能的,其實可以利用sha1()函式的漏洞來繞過。如果把這兩個欄位構造為陣列,如:?name[]=a&password[]=b,這樣在第一處判斷時兩陣列確實是不同的,但在第二處判斷時由於sha1()函式無法處理陣列型別,將報錯並返回false,if 條件成立,獲得flag。

經驗證md5()函式同樣存在此漏洞。

測試截圖:


2.Symphony

  A less than four characters number, bigger than 999? Maybe the bug is elsewhere.


程式碼如下:

<html>
<head>
	<title>level2</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['password'])) {
	if (is_numeric($_GET['password'])){
		if (strlen($_GET['password']) < 4){
			if ($_GET['password'] > 999)
				die('Flag: '.$flag);
			else
				print '<p class="alert">Too little</p>';
		} else
				print '<p class="alert">Too long</p>';
	} else
		print '<p class="alert">Password is not numeric</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 2</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

這個相對簡單,只要腦洞夠大,能想到數字還能用 1e9 這樣的形式來表示。。。

3.Northeastern Univ.

  Of course, a timing attack might be the answer, but I'm quite sure that you can do better than that.

程式碼如下:

<html>
<head>
	<title>level3</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['password'])) {
    if (strcmp($_GET['password'], $flag) == 0)
		die('Flag: '.$flag);
    else
		print '<p class="alert">Invalid password.</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 3</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

乍看也算是個比較嚴密的驗證邏輯,但正如第一題一樣,strcmp()函式也只能處理字串引數,傳個數組進去就能返回false,又由於它與0的比較用的是==而非===(允許型別轉換後比較),就滿足了這個 if 的條件。Payload:?password[]=a

4.Museum of Fine Arts
  Because cryptography is hard, we only implemented a hand-made PRNG. What could possibly go wrong?

程式碼如下:

<html>
<head>
	<title>level4</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
session_start(); 

require 'flag.php';

if (isset ($_GET['password'])) {
    if ($_GET['password'] == $_SESSION['password'])
        die ('Flag: '.$flag);
    else
        print '<p class="alert">Wrong guess.</p>';
}

// Unpredictable seed
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 4</a>
        </div>

		<ul class="list">
		<?php
		for ($i=0; $i<3; $i++)
			print '<li>' . mt_rand (0, 0xffffff) . '</li>';
		$_SESSION['password'] = mt_rand (0, 0xffffff);
		?>
		</ul>

        <form method="get">
                <input type="text" required name="password" placeholder="Next number" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

最初看完以後感覺不可做,莫非真要分析出rand()函式的規律?!但是那就不是CTF的難度了吧。。。後來發現如果將session手動清除掉,然後password欄位也提交為空,不就可以繞過了嘛,也是蠻笨的。。。


5.Longwood Medical
  Because we dont trust mysqli_real_escape_string, we wrote our own military-grade sanitization method.

程式碼如下:

<html>
<head>
	<title>level5</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php

require 'flag.php';

if (isset ($_GET['name']) and isset ($_GET['password'])) {
    $name = $_GET['name'];
    $password = $_GET['password'];

    if (ctype_alnum ($name) and ctype_alnum ($password)) {
        $request = 'SELECT login FROM user where login = ' . $name . ' AND password = ' . $password . ';';
        $db = new SQLite3 (sha1($flag).'.db', SQLITE3_OPEN_READONLY); // Ghetto anti-database-download
        $result = $db->querySingle ($request);
        $db->close ();

        if ($result === FALSE)
            echo '<p class="alert">"Invalid login or password</p>';
        else
            die('Flag: ' . $flag);
    } else
        echo '<p class="alert">Invalid chars detected</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 5</a>
        </div>

        <form method="get">
                <input type="text" required name="name" placeholder="Name"/><br/>
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

注入題,程式碼邏輯還不是很明白。其他人的Payload:?name=0&password=0,為什麼可注還沒有看懂。。。

6.Brigham Circle
  Sanitization is hard, lets use regexp!

程式碼如下:

<html>
<head>
	<title>level6</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset ($_GET['password'])) {
	if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
		echo '<p class="alert">You password must be alphanumeric</p>';
	else if (strpos ($_GET['password'], '--') !== FALSE)
		die('Flag: ' . $flag);
	else
		echo '<p class="alert">Invalid password</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 6</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

關於ereg()函式的一個漏洞——%00截斷,可以構造一個這樣的提交:?password=a%00--,ereg()匹配到%00就截止了,所以會認為提交串合法,但strpos()不受此影響,成功繞過。

此外還有一個非常微妙的方法。把password構造為陣列,如:?password[]=a,由於ereg()也是隻能處理字串的,遇到陣列做引數返回NULL,注意第一處判斷用的是 === ,要求型別也相同,而NULL跟FALSE型別是不同的;第二處判斷strpos()的引數同樣不能為陣列,否則返回NULL,而判斷用的是 !== ,所以這裡的條件成立,也能得到flag。

注:這裡的第二處判斷 !== 並非多此一舉,因為當strpos()返回位置為0時用 == 將不知道到底是找到了還是沒找到,於是不轉換型別的 === 或 !== 是實際中普遍使用的。