1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.util.http;
35
36 import java.util.Arrays;
37 import java.util.Enumeration;
38 import java.util.regex.Pattern;
39
40 import javax.servlet.http.HttpServletRequest;
41
42 import org.apache.commons.lang3.StringUtils;
43 import org.apache.logging.log4j.LogManager;
44 import org.apache.logging.log4j.Logger;
45 import org.springframework.util.AntPathMatcher;
46
47 import fr.paris.lutece.portal.service.util.AppPathService;
48 import fr.paris.lutece.portal.service.util.AppPropertiesService;
49 import fr.paris.lutece.portal.web.LocalVariables;
50 import fr.paris.lutece.util.string.StringUtil;
51
52
53
54
55
56 public final class SecurityUtil
57 {
58 private static final String LOGGER_NAME = "lutece.security.http";
59 private static final String CONSTANT_HTTP_HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
60 private static final String PATTERN_IP_ADDRESS = "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
61 private static final String CONSTANT_COMMA = ",";
62 private static final String [ ] XXE_TERMS = {
63 "!DOCTYPE", "!ELEMENT", "!ENTITY"
64 };
65 private static final String [ ] PATH_MANIPULATION = {
66 "..", "/", "\\"
67 };
68
69 public static final String PROPERTY_REDIRECT_URL_SAFE_PATTERNS = "lutece.security.redirectUrlSafePatterns";
70 public static final String PROPERTY_REDIRECT_URL_BLOCKED_SCHEMES = "lutece.security.redirectUrlBlockedSchemes";
71 public static final String PROPERTY_REDIRECT_URL_BLOCKED_CHARACTERS_PATTERNS = "lutece.security.redirectUrlBlockedCharactersPatterns";
72 private static Pattern PATTERN_URL_SAFE ;
73
74 public static final Logger _log = LogManager.getLogger( LOGGER_NAME );
75
76
77
78
79 private SecurityUtil( )
80 {
81 }
82
83
84
85
86
87
88
89
90 public static boolean containsCleanParameters( HttpServletRequest request )
91 {
92 return containsCleanParameters( request, null );
93 }
94
95
96
97
98
99
100
101
102
103
104 public static boolean containsCleanParameters( HttpServletRequest request, String strXssCharacters )
105 {
106 String key;
107 String [ ] values;
108 Enumeration<String> e = request.getParameterNames( );
109
110 while ( e.hasMoreElements( ) )
111 {
112 key = e.nextElement( );
113 values = request.getParameterValues( key );
114
115 int length = values.length;
116
117 for ( int i = 0; i < length; i++ )
118 {
119 if ( SecurityUtil.containsXssCharacters( request, values [i], strXssCharacters ) || SecurityUtil.containsXssCharacters( request, key, strXssCharacters ) )
120 {
121 _log.warn( "SECURITY WARNING : INVALID REQUEST PARAMETERS {}", ( ) -> dumpRequest( request ) );
122
123 return false;
124 }
125 }
126 }
127
128 return true;
129 }
130
131
132
133
134
135
136
137
138
139
140 public static boolean containsXssCharacters( HttpServletRequest request, String strString )
141 {
142 return containsXssCharacters( request, strString, null );
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156 public static boolean containsXssCharacters( HttpServletRequest request, String strValue, String strXssCharacters )
157 {
158 boolean bContains = ( strXssCharacters == null ) ? StringUtil.containsXssCharacters( strValue )
159 : StringUtil.containsXssCharacters( strValue, strXssCharacters );
160
161 if ( bContains )
162 {
163 _log.warn( "SECURITY WARNING : XSS CHARACTERS DETECTED {}", ( ) -> dumpRequest( request ) );
164 }
165
166 return bContains;
167 }
168
169
170
171
172
173
174
175
176 public static boolean containsXmlExternalEntityInjectionTerms( String strValue )
177 {
178 for ( String strTerm : XXE_TERMS )
179 {
180 if ( StringUtils.indexOfIgnoreCase( strValue, strTerm ) >= 0 )
181 {
182 _log.warn( "SECURITY WARNING : XXE TERMS DETECTED : {}", ( ) -> dumpRequest( LocalVariables.getRequest( ) ) );
183 return true;
184 }
185 }
186 return false;
187 }
188
189
190
191
192
193
194
195
196
197
198 public static boolean containsPathManipulationChars( HttpServletRequest request, String strValue )
199 {
200 for ( String strTerm : PATH_MANIPULATION )
201 {
202 if ( strValue.contains( strTerm ) )
203 {
204 _log.warn( "SECURITY WARNING : PATH_MANIPULATION DETECTED : {}", ( ) -> dumpRequest( request ) );
205 return true;
206 }
207 }
208 return false;
209 }
210
211
212
213
214
215
216
217
218 public static String dumpRequest( HttpServletRequest request )
219 {
220 StringBuilder sbDump = new StringBuilder( "\r\n Request Dump : \r\n" );
221 if ( request != null )
222 {
223 dumpTitle( sbDump, "Request variables" );
224 dumpVariables( sbDump, request );
225 dumpTitle( sbDump, "Request parameters" );
226 dumpParameters( sbDump, request );
227 dumpTitle( sbDump, "Request headers" );
228 dumpHeaders( sbDump, request );
229 }
230 else
231 {
232 sbDump.append( "no request provided." );
233 }
234
235 return sbDump.toString( );
236 }
237
238
239
240
241
242
243
244
245 public static String getRealIp( HttpServletRequest request )
246 {
247 String strIPAddress = request.getHeader( CONSTANT_HTTP_HEADER_X_FORWARDED_FOR );
248
249 if ( strIPAddress != null )
250 {
251 while ( !strIPAddress.matches( PATTERN_IP_ADDRESS ) && strIPAddress.contains( CONSTANT_COMMA ) )
252 {
253 String strIpForwarded = strIPAddress.substring( 0, strIPAddress.indexOf( CONSTANT_COMMA ) );
254 strIPAddress = strIPAddress.substring( strIPAddress.indexOf( CONSTANT_COMMA ) ).replaceFirst( CONSTANT_COMMA, StringUtils.EMPTY ).trim( );
255
256 if ( ( strIpForwarded != null ) && strIpForwarded.matches( PATTERN_IP_ADDRESS ) )
257 {
258 strIPAddress = strIpForwarded;
259 }
260 }
261
262 if ( !strIPAddress.matches( PATTERN_IP_ADDRESS ) )
263 {
264 strIPAddress = request.getRemoteAddr( );
265 }
266 }
267 else
268 {
269 strIPAddress = request.getRemoteAddr( );
270 }
271
272 return strIPAddress;
273 }
274
275
276
277
278
279
280
281
282
283
284 public static boolean isInternalRedirectUrlSafe( String strUrl, HttpServletRequest request )
285 {
286 String strAntPathMatcherPatternsValues = AppPropertiesService.getProperty( SecurityUtil.PROPERTY_REDIRECT_URL_SAFE_PATTERNS );
287
288 return isInternalRedirectUrlSafe( strUrl, request, strAntPathMatcherPatternsValues );
289 }
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 public static boolean isInternalRedirectUrlSafe( String strUrl, HttpServletRequest request, String strAntPathMatcherPatterns )
311 {
312
313 if ( StringUtils.isBlank( strUrl ) )
314 {
315 return true;
316 }
317
318 String strRedirecUrlBlockedSchemes=AppPropertiesService.getProperty(PROPERTY_REDIRECT_URL_BLOCKED_SCHEMES);
319 String strRedirecUrlBlockedCharactersPatterns=AppPropertiesService.getProperty(PROPERTY_REDIRECT_URL_BLOCKED_CHARACTERS_PATTERNS);
320 if( PATTERN_URL_SAFE == null && strRedirecUrlBlockedCharactersPatterns !=null )
321 {
322 PATTERN_URL_SAFE = Pattern.compile( strRedirecUrlBlockedCharactersPatterns );
323 }
324
325
326 if( (strRedirecUrlBlockedCharactersPatterns == null|| !PATTERN_URL_SAFE.matcher( strUrl ).find( ))
327 && strRedirecUrlBlockedSchemes != null && !Arrays.stream(strRedirecUrlBlockedSchemes.split(CONSTANT_COMMA)).anyMatch(x->strUrl.startsWith(x)))
328 {
329 return true;
330
331 }
332
333
334 if ( strUrl.startsWith( AppPathService.getBaseUrl( request ) ) )
335 {
336 return true;
337 }
338
339
340 if ( !StringUtils.isBlank( strAntPathMatcherPatterns ) )
341 {
342 AntPathMatcher pathMatcher = new AntPathMatcher( );
343
344 String [ ] strAntPathMatcherPatternsTab = strAntPathMatcherPatterns.split( CONSTANT_COMMA );
345 for ( String pattern : strAntPathMatcherPatternsTab )
346 {
347 if ( pattern != null && pathMatcher.match( pattern, strUrl ) )
348 {
349 return true;
350 }
351 }
352 }
353
354
355 _log.warn( "SECURITY WARNING : OPEN_REDIRECT DETECTED : {}", ( ) -> dumpRequest( request ) );
356
357 return false;
358
359 }
360
361
362
363
364
365
366
367
368 public static String logForgingProtect( String strUserInputData )
369 {
370 int nCharCount = strUserInputData.length( );
371 int nLineCount = StringUtils.countMatches( strUserInputData, "\n" );
372 String strPrefixedLines = strUserInputData.replace( "\n", "\n** " );
373 return "\n** USER INPUT DATA : BEGIN (" + nLineCount + " lines and " + nCharCount + " chars) ** \n" + strPrefixedLines + "\n** USER INPUT DATA : END\n";
374 }
375
376
377
378
379
380
381
382
383
384 private static void dumpTitle( StringBuilder sbDump, String strTitle )
385 {
386 sbDump.append( "** " );
387 sbDump.append( strTitle );
388 sbDump.append( " **\r\n" );
389 }
390
391
392
393
394
395
396
397
398
399 private static void dumpVariables( StringBuilder sb, HttpServletRequest request )
400 {
401 dumpVariable( sb, "AUTH_TYPE", request.getAuthType( ) );
402 dumpVariable( sb, "REQUEST_METHOD", request.getMethod( ) );
403 dumpVariable( sb, "PATH_INFO", request.getPathInfo( ) );
404 dumpVariable( sb, "PATH_TRANSLATED", request.getPathTranslated( ) );
405 dumpVariable( sb, "QUERY_STRING", request.getQueryString( ) );
406 dumpVariable( sb, "REQUEST_URI", request.getRequestURI( ) );
407 dumpVariable( sb, "SCRIPT_NAME", request.getServletPath( ) );
408 dumpVariable( sb, "LOCAL_ADDR", request.getLocalAddr( ) );
409 dumpVariable( sb, "SERVER_PROTOCOL", request.getProtocol( ) );
410 dumpVariable( sb, "REMOTE_ADDR", request.getRemoteAddr( ) );
411 dumpVariable( sb, "REMOTE_HOST", request.getRemoteHost( ) );
412 dumpVariable( sb, "HTTPS", request.getScheme( ) );
413 dumpVariable( sb, "SERVER_NAME", request.getServerName( ) );
414 dumpVariable( sb, "SERVER_PORT", String.valueOf( request.getServerPort( ) ) );
415 }
416
417
418
419
420
421
422
423
424
425 private static void dumpHeaders( StringBuilder sb, HttpServletRequest request )
426 {
427 Enumeration<String> values;
428 String key;
429 Enumeration<String> headers = request.getHeaderNames( );
430
431 while ( headers.hasMoreElements( ) )
432 {
433 key = headers.nextElement( );
434 values = request.getHeaders( key );
435
436 while ( values.hasMoreElements( ) )
437 {
438 dumpVariable( sb, key, values.nextElement( ) );
439 }
440 }
441 }
442
443
444
445
446
447
448
449
450
451 private static void dumpParameters( StringBuilder sb, HttpServletRequest request )
452 {
453 String key;
454 String [ ] values;
455
456 Enumeration<String> e = request.getParameterNames( );
457
458 while ( e.hasMoreElements( ) )
459 {
460 key = e.nextElement( );
461 values = request.getParameterValues( key );
462
463 int length = values.length;
464
465 for ( int i = 0; i < length; i++ )
466 {
467 dumpVariable( sb, key, values [i] );
468 }
469 }
470 }
471
472
473
474
475
476
477
478
479
480
481
482 private static void dumpVariable( StringBuilder sb, String strName, String strValue )
483 {
484 sb.append( strName );
485 sb.append( " : \"" );
486 sb.append( strValue );
487 sb.append( "\"\r\n" );
488 }
489 }