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