PHP-CGI远程代码执行漏洞分析与防范
发布时间 - 2026-01-11 01:00:34 点击率:次CVE-2012-1823出来时据说是“PHP远程代码执行漏洞”,曾经也“轰动一时”,当时的我只是刚踏入安全门的一个小菜,直到前段时间tomato师傅让我看一个案例,我才想起来这个漏洞。通过在 Vulhub 中对这个漏洞环境的搭建与漏洞原理的分析,我觉得还挺有意思的,故写出一篇文章来,和大家分享。

首先,介绍一下PHP的运行模式。
下载PHP源码,可以看到其中有个目录叫sapi。sapi在PHP中的作用,类似于一个消息的“传递者”,比如我在《 Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 》一文中介绍的fpm,他的作用就是接受Web容器通过fastcgi协议封装好的数据,并交给PHP解释器执行。
除了fpm,最常见的sapi应该是用于Apache的mod_php,这个sapi用于php和apache之间的数据交换。
php-cgi也是一个sapi。在远古的时候,web应用的运行方式很简单,web容器接收到http数据包后,拿到用户请求的文件(cgi脚本),并fork出一个子进程(解释器)去执行这个文件,然后拿到执行结果,直接返回给用户,同时这个解释器子进程也就结束了。基于bash、perl等语言的web应用多半都是以这种方式来执行,这种执行方式一般就被称为cgi,在安装Apache的时候默认有一个cgi-bin目录,最早就是放置这些cgi脚本用的。
但cgi模式有个致命的缺点,众所周知,进程的创建和调度都是有一定消耗的,而且进程的数量也不是无限的。所以,基于cgi模式运行的网站通常不能同时接受大量请求,否则每个请求生成一个子进程,就有可能把服务器挤爆。于是后来就有了fastcgi,fastcgi进程可以将自己一直运行在后台,并通过fastcgi协议接受数据包,执行后返回结果,但自身并不退出。
php有一个叫php-cgi的sapi,php-cgi有两个功能,一是提供cgi方式的交互,二是提供fastcgi方式的交互。也就说,我们可以像perl一样,让web容器直接fork一个php-cgi进程执行某脚本;也可以在后台运行 php-cgi -b 127.0.0.1:9000 (php-cgi作为fastcgi的管理器),并让web容器用fastcgi协议和9000交互。
那我之前说的fpm又是什么呢?为什么php有两个fastcgi管理器?php确实有两个fastcgi管理器,php-cgi可以以fastcgi模式运行,fpm也是以fastcgi模式运行。但fpm是php在5.3版本以后引入的,是一个更高效的fastcgi管理器,其诸多优点我就不多说了,可以自己去翻翻源码。因为fpm优点更多,所以现在越来越多的web应用使用php-fpm去运行php。
回到本漏洞。CVE-2012-1823就是php-cgi这个sapi出现的漏洞,我上面介绍了php-cgi提供的两种运行方式:cgi和fastcgi,本漏洞只出现在以cgi模式运行的php中。
这个漏洞简单来说,就是用户请求的querystring被作为了php-cgi的参数,最终导致了一系列结果。
探究一下原理, RFC3875 中规定,当querystring中不包含没有解码的 = 号的情况下,要将querystring作为cgi的参数传入。所以,Apache服务器按要求实现了这个功能。
但PHP并没有注意到RFC的这一个规则,也许是曾经注意并处理了,处理方法就是web上下文中不允许传入参数。但在2004年的时候某个开发者发表过这么一段言论:
From: Rasmus Lerdorf <rasmus <at> lerdorf.com>
Subject: [PHP-DEV] php-cgi command line switch memory check
Newsgroups: gmane.comp.php.devel
Date: 2004-02-04 23:26:41 GMT (7 years, 49 weeks, 3 days, 20 hours and 39 minutes ago)
In our SAPI cgi we have a check along these lines:
if (getenv("SERVER_SOFTWARE")
|| getenv("SERVER_NAME")
|| getenv("GATEWAY_INTERFACE")
|| getenv("REQUEST_METHOD")) {
cgi = 1;
}
if(!cgi) getopt(...)
As in, we do not parse command line args for the cgi binary if we are
running in a web context. At the same time our regression testing system
tries to use the cgi binary and it sets these variables in order to
properly test GET/POST requests. From the regression testing system we
use -d extensively to override ini settings to make sure our test
environment is sane. Of course these two ideas conflict, so currently our
regression testing is somewhat broken. We haven't noticed because we
don't have many tests that have GET/POST data and we rarely build the cgi
binary.
The point of the question here is if anybody remembers why we decided not
to parse command line args for the cgi version? I could easily see it
being useful to be able to write a cgi script like:
#!/usr/local/bin/php-cgi -d include_path=/path
<?php
...
?>
and have it work both from the command line and from a web context.
As far as I can tell this wouldn't conflict with anything, but somebody at
some point must have had a reason for disallowing this.
-Rasmus
显然,这位开发者是为了方便使用类似 #!/usr/local/bin/php-cgi -d include_path=/path 的写法来进行测试,认为不应该限制php-cgi接受命令行参数,而且这个功能不和其他代码有任何冲突。
于是, if(!cgi) getopt(...) 被删掉了。
但显然,根据RFC中对于command line的说明,命令行参数不光可以通过 #!/usr/local/bin/php-cgi -d include_path=/path 的方式传入php-cgi,更可以通过querystring的方式传入。
这就是本漏洞的历史成因。
那么,可控命令行参数,能做些什么事。
通过阅读源码,我发现cgi模式下有如下一些参数可用:
-c 指定php.ini文件的位置
-n 不要加载php.ini文件
-d 指定配置项
-b 启动fastcgi进程
-s 显示文件源码
-T 执行指定次该文件
-h 和 -? 显示帮助
最简单的利用方式,当然就是 -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的重要性。
# php远程代码执行漏洞
# php-cgi远程执行漏洞
# fastcgi
# 远程代码执行漏洞
# PHP中的变量覆盖漏洞深入解析
# PHP网站常见安全漏洞
# 及相应防范措施总结
# phpcmsv9.0任意文件上传漏洞解析
# phpMyAdmin通过密码漏洞留后门文件
# PHP5.0 TIDY_PARSE_FILE缓冲区溢出漏洞的解决方案
# 由php中字符offset特征造成的绕过漏洞详解
# 你不知道的文件上传漏洞php代码分析
# PHP序列化/对象注入漏洞分析
# PHP常见漏洞攻击分析
# PHP编程中的常见漏洞和代码实例
# 详解各种PHP函数漏洞
# 管理器
# 第一个
# 命令行
# 进行了
# 有个
# 可以通过
# 有两个
# 数据包
# 中不
# 都是
# 是一个
# 情况下
# 有意思
# 我就
# 这一
# 我在
# 我看
# 我觉得
# 又是
# 也就
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
Bootstrap CSS布局之列表
Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控
大连网站制作公司哪家好一点,大连买房网站哪个好?
如何在香港免费服务器上快速搭建网站?
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音
重庆市网站制作公司,重庆招聘网站哪个好?
制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?
Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门
Python图片处理进阶教程_Pillow滤镜与图像增强
Laravel中的Facade(门面)到底是什么原理
Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道
如何在Tomcat中配置并部署网站项目?
微信小程序 配置文件详细介绍
PythonWeb开发入门教程_Flask快速构建Web应用
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
如何快速生成橙子建站落地页链接?
微博html5版本怎么弄发超话_超话进入入口及发帖格式要求【教程】
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
如何获取免费开源的自助建站系统源码?
HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】
1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤
Laravel怎么在Controller之外的地方验证数据
如何在阿里云虚拟主机上快速搭建个人网站?
如何有效防御Web建站篡改攻击?
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧
昵图网官网入口 昵图网素材平台官方入口
如何快速搭建高效香港服务器网站?
Laravel Blade模板引擎语法_Laravel Blade布局继承用法
进行网站优化必须要坚持的四大原则
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
教学论文网站制作软件有哪些,写论文用什么软件
?
深圳网站制作的公司有哪些,dido官方网站?
js代码实现下拉菜单【推荐】
Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】
标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
微信小程序 HTTPS报错整理常见问题及解决方案
Android GridView 滑动条设置一直显示状态(推荐)
高防服务器如何保障网站安全无虞?
Mybatis 中的insertOrUpdate操作
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
php做exe能调用系统命令吗_执行cmd指令实现方式【详解】
iOS验证手机号的正则表达式
使用PHP下载CSS文件中的所有图片【几行代码即可实现】
香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧

