最简单的利用方式,当然就是 -s ,可以直接显示源码:

但阅读过我写的fastcgi那篇文章的同学应该很快就想到了一个更好的利用方法:通过使用 -d 指定 auto_prepend_file 来制造任意文件读取漏洞,执行任意代码:

注意,空格用 + 或 %20 代替, = 用url编码代替。
这个漏洞被爆出来以后,PHP官方对其进行了修补,发布了新版本5.4.2及5.3.12,但这个修复是不完全的,可以被绕过,进而衍生出CVE-2012-2311漏洞。
PHP的修复方法是对 - 进行了检查:
if(query_string = getenv("QUERY_STRING")) {
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
if(*decoded_query_string == '-' && strchr(decoded_query_string, '=') == NULL) {
skip_getopt = 1;
}
free(decoded_query_string);
}
可见,获取querystring后进行解码,如果第一个字符是 - 则设置skip_getopt,也就是不要获取命令行参数。
这个修复方法不安全的地方在于,如果运维对php-cgi进行了一层封装的情况下:
#!/bin/sh exec /usr/local/bin/php-cgi $*
通过使用空白符加 - 的方式,也能传入参数。这时候querystring的第一个字符就是空白符而不是 - 了,绕过了上述检查。
于是,php5.4.3和php5.3.13中继续进行修改:
if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
/* we've got query string that has no = - apache CGI will pass it to command line */
unsigned char *p;
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
for (p = decoded_query_string; *p && *p <= ' '; p++) {
/* skip all leading spaces */
}
if(*p == '-') {
skip_getopt = 1;
}
free(decoded_query_string);
}
先跳过所有空白符(小于等于空格的所有字符),再判断第一个字符是否是 - 。
这个漏洞在当年的影响应该说中等。因为PHP-CGI这个SAPI在漏洞出现的时间点,因为其性能等问题,已经在慢慢退出历史舞台了。但考虑到PHP这个在Web领域举足轻重的语言,跨越多年,用量巨大,很多老的设备、服务器仍在运行有漏洞的版本和PHP-CGI,所以影响也不能低估。
不过,在2017年的今天,我分析这个漏洞当然已经不能谈影响了,只是其思路确实比较有意思,又让我领会了一次阅读RFC的重要性。







