“菜如狗”
前言
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}
后记
两天就这么两道题,真是深深滴感觉自己菜,在从腿毛挂件进化成大腿的路上任重道远啊。