3333 */
3434package fr .paris .lutece .util .http ;
3535
36+ import fr .paris .lutece .portal .service .util .AppPathService ;
37+ import fr .paris .lutece .portal .service .util .AppPropertiesService ;
3638import fr .paris .lutece .portal .web .LocalVariables ;
3739import fr .paris .lutece .util .string .StringUtil ;
3840
4345import java .util .Enumeration ;
4446
4547import javax .servlet .http .HttpServletRequest ;
48+ import org .springframework .util .AntPathMatcher ;
4649
4750/**
4851 * Security utils
@@ -61,6 +64,8 @@ public final class SecurityUtil
6164 ".." , "/" , "\\ "
6265 };
6366
67+ public static final String PROPERTY_REDIRECT_URL_SAFE_PATTERNS = "lutece.security.redirectUrlSafePatterns" ;
68+
6469 // private static final String PATTERN_CLEAN_PARAMETER = "^[\\w/]+$+";
6570
6671 /**
@@ -266,6 +271,79 @@ public static String getRealIp( HttpServletRequest request )
266271
267272 return strIPAddress ;
268273 }
274+
275+ /**
276+ * Validate a forward URL to avoid open redirect
277+ * [see isRedirectUrlSafe(String, HttpServletRequest, String)]
278+ *
279+ * @param strUrl
280+ * @param request
281+ * @return true if valid
282+ */
283+ public static boolean isRedirectUrlSafe ( String strUrl , HttpServletRequest request )
284+ {
285+ String strAntPathMatcherPatternsValues = AppPropertiesService .getProperty ( SecurityUtil .PROPERTY_REDIRECT_URL_SAFE_PATTERNS );
286+
287+ return isRedirectUrlSafe ( strUrl , request , strAntPathMatcherPatternsValues );
288+ }
289+
290+
291+ /**
292+ * Validate a redirect URL to avoid open redirect.
293+ * (Use this function only if the use of url redirect keys is not possible)
294+ *
295+ * the url should :
296+ * - not be blank (null or empty string or spaces)
297+ * - not start with "http://" or "https://" or "//" OR match the base URL or any URL in the pattern list
298+ *
299+ * example with a base url "https://lutece.fr/ :
300+ * - valid : myapp/jsp/site/Portal.jsp , Another.jsp , https://lutece.fr/myapp/jsp/site/Portal.jsp
301+ * - invalid : http://anothersite.com , https://anothersite.com , //anothersite.com , file://my.txt , ...
302+ *
303+ *
304+ * @param strUrl the Url to validate
305+ * @param request the current request (containing the baseUrl)
306+ * @param strAntPathMatcherPatterns a comma separated list of AntPathMatcher patterns, as "http://**.lutece.com,https://**.lutece.com"
307+ * @return true if valid
308+ */
309+ public static boolean isRedirectUrlSafe ( String strUrl , HttpServletRequest request , String strAntPathMatcherPatterns )
310+ {
311+
312+ if ( StringUtils .isBlank ( strUrl ) ) return true ; // this is not a valid redirect Url, but it is not unsafe
313+
314+ // filter schemes
315+ if ( !strUrl .startsWith ( "//" )
316+ && !strUrl .startsWith ("http:" )
317+ && !strUrl .startsWith ("https:" )
318+ && !strUrl .contains ( "://" )
319+ && !strUrl .startsWith ("javascript:" ) )
320+ return true ; // should be a relative path
321+
322+ // compare with current baseUrl
323+ if ( strUrl .startsWith ( AppPathService .getBaseUrl ( request ) ) )
324+ return true ;
325+
326+ // compare with allowed url patterns
327+ if ( !StringUtils .isBlank ( strAntPathMatcherPatterns ) )
328+ {
329+ AntPathMatcher pathMatcher = new AntPathMatcher ();
330+
331+ String [] strAntPathMatcherPatternsTab = strAntPathMatcherPatterns .split ( CONSTANT_COMMA ) ;
332+ for ( String pattern : strAntPathMatcherPatternsTab )
333+ {
334+ if ( pattern != null && pathMatcher .match ( pattern , strUrl ) )
335+ return true ;
336+ }
337+ }
338+
339+
340+ // the Url does not match the allowed patterns
341+ Logger logger = Logger .getLogger ( LOGGER_NAME );
342+ logger .warn ( "SECURITY WARNING : OPEN_REDIRECT DETECTED : " + dumpRequest ( request ) );
343+
344+ return false ;
345+
346+ }
269347
270348 /**
271349 * Identify user data saved in log files to prevent Log Forging attacks
0 commit comments