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.util.StringUtils;
23  import org.apache.shiro.web.util.WebUtils;
24  
25  import javax.servlet.ServletRequest;
26  import javax.servlet.ServletResponse;
27  import javax.servlet.http.HttpServletRequest;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.List;
31  
32  /**
33   * A request filter that blocks malicious requests. Invalid request will respond with a 400 response code.
34   * <p>
35   * This filter checks and blocks the request if the following characters are found in the request URI:
36   * <ul>
37   *     <li>Semicolon - can be disabled by setting {@code blockSemicolon = false}</li>
38   *     <li>Backslash - can be disabled by setting {@code blockBackslash = false}</li>
39   *     <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>
40   * </ul>
41   *
42   * @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>
43   * @since 1.6
44   */
45  public class InvalidRequestFilter extends AccessControlFilter {
46  
47      private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
48  
49      private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));
50  
51      private boolean blockSemicolon = true;
52  
53      private boolean blockBackslash = !Boolean.getBoolean(WebUtils.ALLOW_BACKSLASH);
54  
55      private boolean blockNonAscii = true;
56  
57      @Override
58      protected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {
59          HttpServletRequest request = WebUtils.toHttp(req);
60          // check the original and decoded values
61          return isValid(request.getRequestURI())      // user request string (not decoded)
62                  && isValid(request.getServletPath()) // decoded servlet part
63                  && isValid(request.getPathInfo());   // decoded path info (may be null)
64      }
65  
66      private boolean isValid(String uri) {
67          return !StringUtils.hasText(uri)
68                 || ( !containsSemicolon(uri)
69                   && !containsBackslash(uri)
70                   && !containsNonAsciiCharacters(uri));
71      }
72  
73      @Override
74      protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
75          WebUtils.toHttp(response).sendError(400, "Invalid request");
76          return false;
77      }
78  
79      private boolean containsSemicolon(String uri) {
80          if (isBlockSemicolon()) {
81              return SEMICOLON.stream().anyMatch(uri::contains);
82          }
83          return false;
84      }
85  
86      private boolean containsBackslash(String uri) {
87          if (isBlockBackslash()) {
88              return BACKSLASH.stream().anyMatch(uri::contains);
89          }
90          return false;
91      }
92  
93      private boolean containsNonAsciiCharacters(String uri) {
94          if (isBlockNonAscii()) {
95              return !containsOnlyPrintableAsciiCharacters(uri);
96          }
97          return false;
98      }
99  
100     private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
101         int length = uri.length();
102         for (int i = 0; i < length; i++) {
103             char c = uri.charAt(i);
104             if (c < '\u0020' || c > '\u007e') {
105                 return false;
106             }
107         }
108         return true;
109     }
110 
111     public boolean isBlockSemicolon() {
112         return blockSemicolon;
113     }
114 
115     public void setBlockSemicolon(boolean blockSemicolon) {
116         this.blockSemicolon = blockSemicolon;
117     }
118 
119     public boolean isBlockBackslash() {
120         return blockBackslash;
121     }
122 
123     public void setBlockBackslash(boolean blockBackslash) {
124         this.blockBackslash = blockBackslash;
125     }
126 
127     public boolean isBlockNonAscii() {
128         return blockNonAscii;
129     }
130 
131     public void setBlockNonAscii(boolean blockNonAscii) {
132         this.blockNonAscii = blockNonAscii;
133     }
134 }