@@ -100,29 +100,31 @@ public class ExternalRedirectScanRule extends AbstractAppParamPlugin
100100 private static final String REDIRECT_SITE = SITE_HOST + OWASP_SUFFIX ;
101101
102102 /** The various (prioritized) payload to try */
103- private static final String [] REDIRECT_TARGETS = {
104- REDIRECT_SITE ,
105- HttpHeader .SCHEME_HTTPS + REDIRECT_SITE ,
106- HttpHeader .SCHEME_HTTPS + REDIRECT_SITE .replace ("." , "%2e" ), // Double encode the dots
107- "5;URL='https://" + REDIRECT_SITE + "'" ,
108- "URL='http://" + REDIRECT_SITE + "'" ,
103+ private enum RedirectPayloads {
104+ PLAIN_SITE (REDIRECT_SITE , false ),
105+ HTTPS_SITE (HttpHeader .SCHEME_HTTPS + REDIRECT_SITE , false ),
106+ // Double encode the dots
107+ HTTPS_PERIOD_ENCODE (HttpHeader .SCHEME_HTTPS + REDIRECT_SITE .replace ("." , "%2e" ), false ),
108+ HTTPS_REFRESH ("5;URL='https://" + REDIRECT_SITE + "'" , false ),
109+ HTTPS_LOCATION ("URL='http://" + REDIRECT_SITE + "'" , false ),
109110 // Simple allow list bypass, ex: https://evil.com?<original_value>
110- // Where " original_value" is whatever the parameter value initially was, ex:
111+ // Where < original_value> is whatever the parameter value initially was, ex:
111112 // https://good.expected.com
112- HttpHeader .SCHEME_HTTPS + REDIRECT_SITE + "/?" + ORIGINAL_VALUE_PLACEHOLDER ,
113- "5;URL='https://" + REDIRECT_SITE + "/?" + ORIGINAL_VALUE_PLACEHOLDER + "'" ,
114- HttpHeader .SCHEME_HTTPS + "\\ " + REDIRECT_SITE ,
115- HttpHeader .SCHEME_HTTP + "\\ " + REDIRECT_SITE ,
116- HttpHeader .SCHEME_HTTP + REDIRECT_SITE ,
117- "//" + REDIRECT_SITE ,
118- "\\ \\ " + REDIRECT_SITE ,
119- "HtTp://" + REDIRECT_SITE ,
120- "HtTpS://" + REDIRECT_SITE ,
121-
122- // http://kotowicz.net/absolute/
123- // I never met real cases for these
124- // to be evaluated in the future
125- /*
113+ HTTPS_ORIG_PARAM (
114+ HttpHeader .SCHEME_HTTPS + REDIRECT_SITE + "/?" + ORIGINAL_VALUE_PLACEHOLDER , true ),
115+ HTTPS_REFRESH_ORIG_PARAM (
116+ "5;URL='https://" + REDIRECT_SITE + "/?" + ORIGINAL_VALUE_PLACEHOLDER + "'" , true ),
117+ HTTPS_WRONG_SLASH (HttpHeader .SCHEME_HTTPS + "\\ " + REDIRECT_SITE , false ),
118+ HTTP_WRONG_SLASH (HttpHeader .SCHEME_HTTP + "\\ " + REDIRECT_SITE , false ),
119+ HTTP (HttpHeader .SCHEME_HTTP + REDIRECT_SITE , false ),
120+ NO_SCHEME ("//" + REDIRECT_SITE , false ),
121+ NO_SCHEME_WRONG_SLASH ("\\ \\ " + REDIRECT_SITE , false ),
122+ HTTPS_MIXED_CASE ("HtTpS://" + REDIRECT_SITE , false ),
123+ HTTP_MIXED_CASE ("HtTp://" + REDIRECT_SITE , false );
124+
125+ /* http://kotowicz.net/absolute/
126+ I never met real cases for these
127+ to be evaluated in the future
126128 "/\\" + REDIRECT_SITE,
127129 "\\/" + REDIRECT_SITE,
128130 "\r \t//" + REDIRECT_SITE,
@@ -133,7 +135,23 @@ public class ExternalRedirectScanRule extends AbstractAppParamPlugin
133135 "://" + REDIRECT_SITE,
134136 ".:." + REDIRECT_SITE
135137 */
136- };
138+
139+ private final String payload ;
140+ private final boolean hasPlaceHolder ;
141+
142+ RedirectPayloads (String payload , boolean hasPlaceHolder ) {
143+ this .payload = payload ;
144+ this .hasPlaceHolder = hasPlaceHolder ;
145+ }
146+
147+ public String getPayload () {
148+ return payload ;
149+ }
150+
151+ public boolean hasPlaceHolder () {
152+ return hasPlaceHolder ;
153+ }
154+ }
137155
138156 // Get WASC Vulnerability description
139157 private static final Vulnerability VULN = Vulnerabilities .getDefault ().get ("wasc_38" );
@@ -180,124 +198,92 @@ public String getReference() {
180198 @ Override
181199 public void scan (HttpMessage msg , String param , String value ) {
182200
183- // Number of targets to try
184- int targetCount = 0 ;
185-
186- // Debug only
187201 LOGGER .debug ("Attacking at Attack Strength: {}" , this .getAttackStrength ());
188-
189202 // Figure out how aggressively we should test
190- switch (this .getAttackStrength ()) {
191- case LOW :
192- // Check only for baseline targets (2 reqs / param)
193- targetCount = 3 ;
194- break ;
195-
196- case MEDIUM :
197- // This works out as a total of 9 reqs / param
198- targetCount = 9 ;
199- break ;
200-
201- case HIGH :
202- // This works out as a total of 15 reqs / param
203- targetCount = REDIRECT_TARGETS .length ;
204- break ;
205-
206- case INSANE :
207- // This works out as a total of 15 reqs / param
208- targetCount = REDIRECT_TARGETS .length ;
209- break ;
210-
211- default :
212- break ;
213- }
203+ int payloadCount = getPayloadCount ();
214204
215205 LOGGER .debug (
216206 "Checking [{}][{}], parameter [{}] for Open Redirect Vulnerabilities" ,
217207 getBaseMsg ().getRequestHeader ().getMethod (),
218208 getBaseMsg ().getRequestHeader ().getURI (),
219209 param );
220210
221- // For each target in turn
222- // note that depending on the AttackLevel,
223- // the number of elements that we will try changes.
224- String payload ;
225211 String redirectUrl ;
212+ int payloadIdx = 0 ;
226213
227- for (int h = 0 ; h < targetCount ; h ++) {
228- if (isStop ()) {
214+ // For each payload in turn
215+ // note that depending on the Strength,
216+ // the number of elements that we will try changes.
217+ for (RedirectPayloads payload : RedirectPayloads .values ()) {
218+ if (isStop () || ++payloadIdx == payloadCount ) {
229219 return ;
230220 }
231221
232- payload =
233- REDIRECT_TARGETS [ h ]. contains ( ORIGINAL_VALUE_PLACEHOLDER )
234- ? REDIRECT_TARGETS [ h ]. replace (ORIGINAL_VALUE_PLACEHOLDER , value )
235- : REDIRECT_TARGETS [ h ];
222+ String injection = payload . getPayload ();
223+ if ( payload . hasPlaceHolder ()) {
224+ injection = payload . getPayload (). replace (ORIGINAL_VALUE_PLACEHOLDER , value );
225+ }
236226
237227 // Get a new copy of the original message (request only) for each parameter value to try
238228 HttpMessage testMsg = getNewMsg ();
239- setParameter (testMsg , param , payload );
229+ setParameter (testMsg , param , injection );
240230
241- LOGGER .debug ("Testing [{}] = [{}]" , param , payload );
231+ LOGGER .debug ("Testing [{}] = [{}]" , param , injection );
242232
243233 try {
244234 // Send the request and retrieve the response
245235 // Be careful: we haven't to follow redirect
246236 sendAndReceive (testMsg , false );
247237
248238 String payloadScheme =
249- StringUtils .containsIgnoreCase (payload , HttpHeader .HTTPS )
239+ StringUtils .containsIgnoreCase (injection , HttpHeader .HTTPS )
250240 ? HttpHeader .HTTPS
251241 : HttpHeader .HTTP ;
252- // If it's a meta based injection the use the base url
242+ // If it's a meta based injection then use the base URL
253243 redirectUrl =
254- (payload .startsWith ("5;" ) || payload .startsWith ("URL=" ))
244+ (injection .startsWith ("5;" ) || injection .startsWith ("URL=" ))
255245 ? payloadScheme + "://" + REDIRECT_SITE
256- : payload ;
246+ : injection ;
257247
258248 // Get back if a redirection occurs
259249 int redirectType = isRedirected (redirectUrl , testMsg );
260250
261251 if (redirectType != NO_REDIRECT ) {
262- // We Found IT!
263- // First do logging
264252 LOGGER .debug (
265253 "[External Redirection Found] on parameter [{}] with payload [{}]" ,
266254 param ,
267- payload );
268-
269- buildAlert (param , payload , redirectType , redirectUrl , testMsg ).raise ();
255+ injection );
270256
271- // All done. No need to look for vulnerabilities on subsequent
272- // parameters on the same request (to reduce performance impact)
257+ buildAlert (param , injection , redirectType , redirectUrl , testMsg ).raise ();
273258 return ;
274259 }
275260 } catch (IOException ex ) {
276- // Do not try to internationalize this.. we need an error message in any event..
277- // if it's in English, it's still better than not having it at all.
278261 LOGGER .warn (
279262 "External Redirect vulnerability check failed for parameter [{}] and payload [{}] due to an I/O error" ,
280263 param ,
281- payload ,
264+ injection ,
282265 ex );
283266 }
284267 }
285268 }
286269
270+ private int getPayloadCount () {
271+ return switch (this .getAttackStrength ()) {
272+ case LOW -> 3 ;
273+ case MEDIUM -> 9 ;
274+ case HIGH , INSANE -> RedirectPayloads .values ().length ;
275+ default -> 9 ;
276+ };
277+ }
278+
287279 private String getAlertReference (int redirectType ) {
288- switch (redirectType ) {
289- case REDIRECT_LOCATION_HEADER :
290- return getId () + "-1" ;
291- case REDIRECT_REFRESH_HEADER :
292- return getId () + "-2" ;
293- case REDIRECT_LOCATION_META :
294- case REDIRECT_REFRESH_META :
295- return getId () + "-3" ;
296- case REDIRECT_JAVASCRIPT :
297- return getId () + "-4" ;
298- default :
299- return "" ;
300- }
280+ return switch (redirectType ) {
281+ case REDIRECT_LOCATION_HEADER -> getId () + "-1" ;
282+ case REDIRECT_REFRESH_HEADER -> getId () + "-2" ;
283+ case REDIRECT_LOCATION_META , REDIRECT_REFRESH_META -> getId () + "-3" ;
284+ case REDIRECT_JAVASCRIPT -> getId () + "-4" ;
285+ default -> "" ;
286+ };
301287 }
302288
303289 private AlertBuilder buildAlert (
0 commit comments