55
66
77class telerik (BaseModule ):
8+ """
9+ Test for endpoints associated with Telerik.Web.UI.dll
10+
11+ Telerik.Web.UI.WebResource.axd (CVE-2017-11317)
12+ Telerik.Web.UI.DialogHandler.aspx (CVE-2017-9248)
13+ Telerik.Web.UI.SpellCheckHandler.axd (associated with CVE-2017-9248)
14+ ChartImage.axd (CVE-2019-19790)
15+
16+ For the Telerik Report Server vulnerability (CVE-2024-4358) Use the Nuclei Template: (https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2024/CVE-2024-4358.yaml)
17+
18+ With exploit_RAU_crypto enabled, the module will attempt to exploit CVE-2017-11317. THIS WILL UPLOAD A (benign) FILE IF SUCCESSFUL.
19+
20+ Will dedupe to host by default (running against first received URL). With include_subdirs enabled, will run against every directory.
21+ """
22+
823 watched_events = ["URL" , "HTTP_RESPONSE" ]
924 produced_events = ["VULNERABILITY" , "FINDING" ]
1025 flags = ["active" , "aggressive" , "web-thorough" ]
@@ -139,8 +154,11 @@ class telerik(BaseModule):
139154
140155 RAUConfirmed = []
141156
142- options = {"exploit_RAU_crypto" : False }
143- options_desc = {"exploit_RAU_crypto" : "Attempt to confirm any RAU AXD detections are vulnerable" }
157+ options = {"exploit_RAU_crypto" : False , "include_subdirs" : False }
158+ options_desc = {
159+ "exploit_RAU_crypto" : "Attempt to confirm any RAU AXD detections are vulnerable" ,
160+ "include_subdirs" : "Include subdirectories in the scan (off by default)" , # will create many finding events if used in conjunction with web spider or ffuf
161+ }
144162
145163 in_scope_only = True
146164
@@ -162,19 +180,33 @@ class telerik(BaseModule):
162180
163181 _module_threads = 5
164182
183+ @staticmethod
184+ def normalize_url (url ):
185+ return str (url .rstrip ("/" ) + "/" ).lower ()
186+
165187 def _incoming_dedup_hash (self , event ):
166188 if event .type == "URL" :
167- return hash (event .host )
168- else :
169- return hash (event .data ["url" ])
189+ if self .config .get ("include_subdirs" ) is True :
190+ return hash (f"{ event .type } { self .normalize_url (event .data )} " )
191+ else :
192+ return hash (f"{ event .type } { event .netloc } " )
193+ else : # HTTP_RESPONSE
194+ return hash (f"{ event .type } { event .data ['url' ]} " )
170195
171196 async def handle_event (self , event ):
172197 if event .type == "URL" :
198+ if self .config .get ("include_subdirs" ):
199+ base_url = self .normalize_url (event .data ) # Use the entire URL including subdirectories
200+
201+ else :
202+ base_url = f"{ event .parsed_url .scheme } ://{ event .parsed_url .netloc } /" # path will be omitted
203+
204+ # Check for RAU AXD Handler
173205 webresource = "Telerik.Web.UI.WebResource.axd?type=rau"
174- result , _ = await self .test_detector (event . data , webresource )
206+ result , _ = await self .test_detector (base_url , webresource )
175207 if result :
176208 if "RadAsyncUpload handler is registered successfully" in result .text :
177- self .debug ("Detected Telerik instance (Telerik.Web.UI.WebResource.axd?type=rau)" )
209+ self .verbose ("Detected Telerik instance (Telerik.Web.UI.WebResource.axd?type=rau)" )
178210
179211 probe_data = {
180212 "rauPostData" : (
@@ -211,15 +243,14 @@ async def handle_event(self, event):
211243
212244 description = f"Telerik RAU AXD Handler detected. Verbose Errors Enabled: [{ str (verbose_errors )} ] Version Guess: [{ version } ]"
213245 await self .emit_event (
214- {"host" : str (event .host ), "url" : f"{ event . data } { webresource } " , "description" : description },
246+ {"host" : str (event .host ), "url" : f"{ base_url } { webresource } " , "description" : description },
215247 "FINDING" ,
216248 event ,
217- context = f"{{module}} scanned { event . data } and identified {{event.type}}: Telerik RAU AXD Handler" ,
249+ context = f"{{module}} scanned { base_url } and identified {{event.type}}: Telerik RAU AXD Handler" ,
218250 )
219251 if self .config .get ("exploit_RAU_crypto" ) is True :
220- hostname = urlparse (event .data ).netloc
221- if hostname not in self .RAUConfirmed :
222- self .RAUConfirmed .append (hostname )
252+ if base_url not in self .RAUConfirmed :
253+ self .RAUConfirmed .append (base_url )
223254 root_tool_path = self .scan .helpers .tools_dir / "telerik"
224255 self .debug (root_tool_path )
225256
@@ -242,17 +273,17 @@ async def handle_event(self, event):
242273 "severity" : "CRITICAL" ,
243274 "description" : description ,
244275 "host" : str (event .host ),
245- "url" : f"{ event . data } { webresource } " ,
276+ "url" : f"{ base_url } { webresource } " ,
246277 },
247278 "VULNERABILITY" ,
248279 event ,
249- context = f"{{module}} scanned { event . data } and identified critical {{event.type}}: { description } " ,
280+ context = f"{{module}} scanned { base_url } and identified critical {{event.type}}: { description } " ,
250281 )
251282 break
252283
253284 urls = {}
254285 for dh in self .DialogHandlerUrls :
255- url = self .create_url (event . data , f"{ dh } ?dp=1" )
286+ url = self .create_url (base_url , f"{ dh } ?dp=1" )
256287 urls [url ] = dh
257288
258289 gen = self .helpers .request_batch (list (urls ))
@@ -265,27 +296,27 @@ async def handle_event(self, event):
265296 # tolerate some random errors
266297 if fail_count < 2 :
267298 continue
268- self .debug (f"Cancelling run against { event . data } due to failed request" )
299+ self .debug (f"Cancelling run against { base_url } due to failed request" )
269300 await gen .aclose ()
270301 else :
271302 if "Cannot deserialize dialog parameters" in response .text :
272303 self .debug (f"Detected Telerik UI instance ({ dh } )" )
273304 description = "Telerik DialogHandler detected"
274305 await self .emit_event (
275- {"host" : str (event .host ), "url" : f"{ event . data } { dh } " , "description" : description },
306+ {"host" : str (event .host ), "url" : f"{ base_url } { dh } " , "description" : description },
276307 "FINDING" ,
277308 event ,
278309 )
279310 # Once we have a match we need to stop, because the basic handler (Telerik.Web.UI.DialogHandler.aspx) usually works with a path wildcard
280311 await gen .aclose ()
281312
282313 spellcheckhandler = "Telerik.Web.UI.SpellCheckHandler.axd"
283- result , _ = await self .test_detector (event . data , spellcheckhandler )
314+ result , _ = await self .test_detector (base_url , spellcheckhandler )
284315 status_code = getattr (result , "status_code" , 0 )
285316 # The standard behavior for the spellcheck handler without parameters is a 500
286317 if status_code == 500 :
287318 # Sometimes webapps will just return 500 for everything, so rule out the false positive
288- validate_result , _ = await self .test_detector (event . data , self .helpers .rand_string ())
319+ validate_result , _ = await self .test_detector (base_url , self .helpers .rand_string ())
289320 self .debug (validate_result )
290321 validate_status_code = getattr (validate_result , "status_code" , 0 )
291322 if validate_status_code not in (0 , 500 ):
@@ -294,31 +325,31 @@ async def handle_event(self, event):
294325 await self .emit_event (
295326 {
296327 "host" : str (event .host ),
297- "url" : f"{ event . data } { spellcheckhandler } " ,
328+ "url" : f"{ base_url } { spellcheckhandler } " ,
298329 "description" : description ,
299330 },
300331 "FINDING" ,
301332 event ,
302- context = f"{{module}} scanned { event . data } and identified {{event.type}}: Telerik SpellCheckHandler" ,
333+ context = f"{{module}} scanned { base_url } and identified {{event.type}}: Telerik SpellCheckHandler" ,
303334 )
304335
305336 chartimagehandler = "ChartImage.axd?ImageName=bqYXJAqm315eEd6b%2bY4%2bGqZpe7a1kY0e89gfXli%2bjFw%3d"
306- result , _ = await self .test_detector (event . data , chartimagehandler )
337+ result , _ = await self .test_detector (base_url , chartimagehandler )
307338 status_code = getattr (result , "status_code" , 0 )
308339 if status_code == 200 :
309340 chartimagehandler_error = "ChartImage.axd?ImageName="
310- result_error , _ = await self .test_detector (event . data , chartimagehandler_error )
341+ result_error , _ = await self .test_detector (base_url , chartimagehandler_error )
311342 error_status_code = getattr (result_error , "status_code" , 0 )
312343 if error_status_code not in (0 , 200 ):
313344 await self .emit_event (
314345 {
315346 "host" : str (event .host ),
316- "url" : f"{ event . data } { chartimagehandler } " ,
347+ "url" : f"{ base_url } { chartimagehandler } " ,
317348 "description" : "Telerik ChartImage AXD Handler Detected" ,
318349 },
319350 "FINDING" ,
320351 event ,
321- context = f"{{module}} scanned { event . data } and identified {{event.type}}: Telerik ChartImage AXD Handler" ,
352+ context = f"{{module}} scanned { base_url } and identified {{event.type}}: Telerik ChartImage AXD Handler" ,
322353 )
323354
324355 elif event .type == "HTTP_RESPONSE" :
@@ -348,14 +379,8 @@ async def handle_event(self, event):
348379 context = "{module} searched HTTP_RESPONSE and identified {event.type}: Telerik AsyncUpload" ,
349380 )
350381
351- # Check for RAD Controls in URL
352-
353382 def create_url (self , baseurl , detector ):
354- if not baseurl .endswith ("/" ):
355- url = f"{ baseurl } /{ detector } "
356- else :
357- url = f"{ baseurl } { detector } "
358- return url
383+ return f"{ baseurl } { detector } "
359384
360385 async def test_detector (self , baseurl , detector ):
361386 result = None
0 commit comments