View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.shiro.web.filter;
21  
22  import org.apache.shiro.web.util.WebUtils;
23  
24  import javax.servlet.ServletRequest;
25  import javax.servlet.ServletResponse;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.List;
29  
30  /**
31   * A request filter that blocks malicious requests. Invalid request will respond with a 400 response code.
32   * <p>
33   * This filter checks and blocks the request if the following characters are found in the request URI:
34   * <ul>
35   *     <li>Semicolon - can be disabled by setting {@code blockSemicolon = false}</li>
36   *     <li>Backslash - can be disabled by setting {@code blockBackslash = false}</li>
37   *     <li>Non-ASCII characters - can be disabled by setting {@code blockNonAscii = false}, the ability to disable this check will be removed in future version.</li>
38   * </ul>
39   *
40   * @see <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/firewall/StrictHttpFirewall.html">This class was inspired by Spring Security StrictHttpFirewall</a>
41   * @since 1.6
42   */
43  public class InvalidRequestFilter extends AccessControlFilter {
44  
45      private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
46  
47      private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));
48  
49      private boolean blockSemicolon = true;
50  
51      private boolean blockBackslash = true;
52  
53      private boolean blockNonAscii = true;
54  
55      @Override
56      protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
57          String uri = WebUtils.toHttp(request).getRequestURI();
58          return !containsSemicolon(uri)
59              && !containsBackslash(uri)
60              && !containsNonAsciiCharacters(uri);
61      }
62  
63      @Override
64      protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
65          WebUtils.toHttp(response).sendError(400, "Invalid request");
66          return false;
67      }
68  
69      private boolean containsSemicolon(String uri) {
70          if (isBlockSemicolon()) {
71              return SEMICOLON.stream().anyMatch(uri::contains);
72          }
73          return false;
74      }
75  
76      private boolean containsBackslash(String uri) {
77          if (isBlockBackslash()) {
78              return BACKSLASH.stream().anyMatch(uri::contains);
79          }
80          return false;
81      }
82  
83      private boolean containsNonAsciiCharacters(String uri) {
84          if (isBlockNonAscii()) {
85              return !containsOnlyPrintableAsciiCharacters(uri);
86          }
87          return false;
88      }
89  
90      private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
91          int length = uri.length();
92          for (int i = 0; i < length; i++) {
93              char c = uri.charAt(i);
94              if (c < '\u0020' || c > '\u007e') {
95                  return false;
96              }
97          }
98          return true;
99      }
100 
101     public boolean isBlockSemicolon() {
102         return blockSemicolon;
103     }
104 
105     public void setBlockSemicolon(boolean blockSemicolon) {
106         this.blockSemicolon = blockSemicolon;
107     }
108 
109     public boolean isBlockBackslash() {
110         return blockBackslash;
111     }
112 
113     public void setBlockBackslash(boolean blockBackslash) {
114         this.blockBackslash = blockBackslash;
115     }
116 
117     public boolean isBlockNonAscii() {
118         return blockNonAscii;
119     }
120 
121     public void setBlockNonAscii(boolean blockNonAscii) {
122         this.blockNonAscii = blockNonAscii;
123     }
124 }