Skip to content

Commit 5338579

Browse files
committed
LUTECE-2206 : Avoid open redirect when modifying a theme
1 parent 342ca3e commit 5338579

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed

src/java/fr/paris/lutece/portal/web/style/ThemesJspBean.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import fr.paris.lutece.portal.service.portal.ThemesService;
4141
import fr.paris.lutece.portal.service.template.AppTemplateService;
4242
import fr.paris.lutece.portal.service.util.AppPathService;
43+
import fr.paris.lutece.portal.service.util.AppPropertiesService;
4344
import fr.paris.lutece.portal.web.admin.AdminFeaturesPageJspBean;
4445
import fr.paris.lutece.portal.web.constants.Messages;
4546
import fr.paris.lutece.util.html.HtmlTemplate;
@@ -138,7 +139,8 @@ public String doModifyUserTheme( HttpServletRequest request, HttpServletResponse
138139
String strTheme = request.getParameter( PARAMETER_THEME );
139140
String strForwardUrl = request.getParameter( PARAMETER_URL );
140141

141-
if ( !SecurityUtil.containsCleanParameters( request ) )
142+
if ( !SecurityUtil.containsCleanParameters( request )
143+
|| !SecurityUtil.isRedirectUrlSafe( strForwardUrl, request ) )
142144
{
143145
return AppPathService.getBaseUrl( request );
144146
}

src/java/fr/paris/lutece/util/http/SecurityUtil.java

+78
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
*/
3434
package fr.paris.lutece.util.http;
3535

36+
import fr.paris.lutece.portal.service.util.AppPathService;
37+
import fr.paris.lutece.portal.service.util.AppPropertiesService;
3638
import fr.paris.lutece.portal.web.LocalVariables;
3739
import fr.paris.lutece.util.string.StringUtil;
3840

@@ -43,6 +45,7 @@
4345
import java.util.Enumeration;
4446

4547
import 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

src/test/java/fr/paris/lutece/util/http/SecurityUtilTest.java

+63
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,67 @@ public void testContainsCleanParameters( )
8484
request.setParameter( "param4", "}" );
8585
assertTrue( SecurityUtil.containsCleanParameters( request ) );
8686
}
87+
88+
89+
/**
90+
* Test of isRedirectUrlSafe method, of class SecurityUtil, to avoid open redirect
91+
*/
92+
@Test
93+
public void testIsRedirectUrlSafe( )
94+
{
95+
System.out.println( "isRedirectUrlSafe" );
96+
97+
MockHttpServletRequest request = new MockHttpServletRequest( );
98+
99+
// Assert False
100+
String strUrl = "http://anothersite.com";
101+
request.setParameter( "url", strUrl );
102+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
103+
104+
strUrl = "//anothersite.com";
105+
request.setParameter( "url", strUrl );
106+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
107+
108+
strUrl = "file://my.txt";
109+
request.setParameter( "url", strUrl );
110+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
111+
112+
strUrl = "javascript:alert('hello');";
113+
request.setParameter( "url", strUrl );
114+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
115+
116+
strUrl = "opera-http://anothersite.com";
117+
request.setParameter( "url", strUrl );
118+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
119+
120+
strUrl = "http://another.subdomain.mylutece.com";
121+
request.setParameter( "url", strUrl );
122+
String strUrlPatterns="http://**.lutece.com,https://**.lutece.com";
123+
assertFalse( SecurityUtil.isRedirectUrlSafe( strUrl, request, strUrlPatterns ) );
124+
125+
// Assert True
126+
strUrl = null;
127+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request) );
128+
129+
strUrl = "";
130+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request) );
131+
132+
strUrl = "/jsp/site/Portal.jsp";
133+
request.setParameter( "url", strUrl );
134+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
135+
136+
strUrl = "Another.jsp";
137+
request.setParameter( "url", strUrl );
138+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
139+
140+
strUrl = "http://localhost/myapp/jsp/site/Portal.jsp";
141+
request.setParameter( "url", strUrl );
142+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request ) );
143+
144+
strUrl = "http://another.subdomain.lutece.com";
145+
request.setParameter( "url", strUrl );
146+
strUrlPatterns="http://**.lutece.com/**,https://**.lutece.com/**";
147+
assertTrue( SecurityUtil.isRedirectUrlSafe( strUrl, request, strUrlPatterns ) );
148+
149+
}
87150
}

webapp/WEB-INF/conf/lutece.properties

+8
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@ askPasswordReinitialization.admin.level=0
227227
input.xss.characters=<>#"
228228
xss.error.message= Les caract\u00e8res &lt; &gt; # &amp; et &quot; sont interdits dans le contenu de votre message.
229229

230+
################################################################################
231+
# Redirect URL safe patterns
232+
#
233+
# The list of "safe" AntPathMatcher patterns used by SecurityUtil.isReturnURLSafe function
234+
# to avoid Open Redirect attacks.
235+
# This should be a comma separated list of AntPathMatcher patterns, ex: "http://**.lutece.com/**,https://**.lutece.com/**"
236+
lutece.security.redirectUrlSafePatterns=
237+
230238
################################################################################
231239
# Paginators
232240
#

0 commit comments

Comments
 (0)