BadInputFilter revisited

Restore the security benefits of BadInputFilter for any Tomcat or Servlet/JSP container implementation

1 2 3 Page 2
Page 2 of 3
  • In method doFilter(), we create a FilterableRequest that wraps the HttpServletRequest parameter and then pass this FilterableRequest to method filterParameters as follows:

    FilterableRequest filterableRequest = new FilterableRequest((HttpServletRequest) request);
    filterParameters(filterableRequest);
    
  • In method filterParameters(), we call getModifiableParameterMap() on the filterable request to gain access to the parameters rather than attempting to “unlock” the immutable Map using reflection.

Essentially the rest of the code in these two methods is modified only slightly from the original BadInputFilter implementation. Listing 2 shows the source code for these two methods in my version of BadInputFilter.

Listing 2. Updated BadInputFilter

@Override
   public void doFilter(ServletRequest request, ServletResponse response,
       FilterChain filterChain) throws IOException, ServletException
     {
       // Skip filtering for non-HTTP requests and responses.
       if (!(request instanceof HttpServletRequest)
           || !(response instanceof HttpServletResponse))
         {
           filterChain.doFilter(request, response);
           return;
         }

       // Only let requests through based on the allows and denies.
       if (processAllowsAndDenies(request, response))
         {
           // Filter the input for potentially dangerous JavaScript
           // code so that bad user input is cleaned out of the request
           // by the time Tomcat begins to perform the request.
           FilterableRequest filterableRequest
               = new FilterableRequest((HttpServletRequest) request);
           filterParameters(filterableRequest);

           // Perform the request.
           filterChain.doFilter(filterableRequest, response);
         }
     }

   /**
    * Filters all existing parameters for potentially dangerous content,
    * and escapes any if they are found.
    *
    * @param request The FilterableRequest that contains the parameters.
    */
   public void filterParameters(FilterableRequest request)
     {
       Map<String, String[]> paramMap = request.getModifiableParameterMap();

       // Loop through each of the substitution patterns.
       for (String patternString : parameterEscapes.keySet())
         {
           Pattern pattern = Pattern.compile(patternString);

           // Loop through the list of parameter names.
           for (String name : paramMap.keySet())
             {
               String[] values = request.getParameterValues(name);

               // See if the name contains the pattern.
               boolean nameMatch;
               Matcher matcher = pattern.matcher(name);
               nameMatch = matcher.find();
               if (nameMatch)
                 {
                   // The parameter’s name matched a pattern, so we
                   // fix it by modifying the name, adding the parameter
                   // back as the new name, and removing the old one.
                   String newName = matcher.replaceAll((String) parameterEscapes
                       .get(patternString));
                   paramMap.remove(name);
                   paramMap.put(newName, values);
                   servletContext.log(“Parameter name “ + name
                       + “ matched pattern \”” + patternString
                       + “\”.  Remote addr: “
                       + ((HttpServletRequest) request).getRemoteAddr());
                 }
             }

           // Loop through the list of parameter values for each name.
           for (String name : paramMap.keySet())
             {
               String[] values = request.getParameterValues(name);
               // Check the parameter’s values for the pattern.
               if (values != null)
                 {
                   for (int j = 0; j < values.length; j++)
                     {
                       String value = values[j];
                       boolean valueMatch;
                       Matcher matcher = pattern.matcher(value);
                       valueMatch = matcher.find();
                       if (valueMatch)
                         {
                           // The value matched, so we modify the value
                           // and then set it back into the array.
                           String newValue;
                           newValue = matcher.replaceAll((String)
                               parameterEscapes.get(patternString));
                           values[j] = newValue;
                           servletContext.log(“Parameter \”” + name
                               + “\”‘s value \”” + value
                               + “\” matched pattern \””
                               + patternString + “\”.  Remote addr: “
                               + ((HttpServletRequest) request).getRemoteAddr());
                           servletContext.log(“newValue =” + newValue);
                         }
                     }
                 }
             }
         }
     }

Configuring BadInputFilter

The Brittain & Darwin book contains sample configuration settings for BadInputFilter that can be added to the web application deployment descriptor (web.xml). Listing 3 shows the sample configuration code given in the book.

Listing 3. Sample configuration for BadInputFilter


   
   <filter>
     <filter-name>BadInputFilter</filter-name>
     <filter-class>com.oreilly.tomcat.filter.BadInputFilter</filter-class>
     <init-param>
       <param-name>deny</param-name>
       <param-value>\x00,\x04,\x08,\x0a,\x0d</param-value>
     </init-param>
     <init-param>
       <param-name>escapeQuotes</param-name>
       <param-value>true</param-value>
     </init-param>
     <init-param>
       <param-name>escapeAngleBrackets</param-name>
       <param-value>true</param-value>
     </init-param>
     <init-param>
       <param-name>escapeJavaScript</param-name>
       <param-value>true</param-value>
     </init-param>
   </filter>
   <filter-mapping>
     <filter-name>BadInputFilter</filter-name>
     <url-pattern>/input_test.jsp</url-pattern>
   </filter-mapping>
   

The minimum change needed to use my version of BadInputFilter is to replace com.oreilly.tomcat.filter.BadInputFilter with com.softmoore.filter.BadInputFilter in the <filter-class> element of this configuration. In addition, I think it’s worth considering a few other possible changes to either the configuration code in Listing 3 or to the original source code for BadInputFilter.

First, Eclipse Kepler (4.3.1) and Java 7 will give several warnings related to generics when compiling the original version of BadInputFilter. This is not surprising because the original code was written when generics were relatively new to Java. At one point in Jason Brittain’s original code there is a SuppressWarnings annotation to get rid of one such warning, but other warnings now exist. There are several ways to get rid of these warnings, but following Jason’s code we can simply add SuppressWarnings annotations as follows:

  1. Add @SuppressWarnings(“rawtypes”) before method processAllowsAndDenies().
  2. Replace @SuppressWarnings(“unchecked”) with @SuppressWarnings({ “unchecked”, “rawtypes” }) before method filterParameters.
  3. Remove @SuppressWarnings(“unchecked”) from the middle of method filterParameters() since the change in item 2 above makes it redundant.

Second, when the configuration parameter escapeQuotes is set to true, as shown in Listing 3, BadInputFilter will replace all occurrences of a single-quote (apostrophe) with the entity “'”. This is an essential defense against several web security attacks, especially SQL injection attacks. It can present problems, however, if one of the parameters is used for a search based on last names and you want to allow searching for names like “O’Neil.” The safest thing to do here is to not make any changes to either the configuration file or BadInputFilter but to unescape the value of the parameter as part of additional validation before performing the search. (Of course, you should always prefer a JDBC PreparedStatement over a simple Statement when accessing the database; doing so not only is potentially more efficient, but PreparedStatements are less susceptible to SQL injection attacks. See the Resources section to learn more about using PreparedStatement for web application security.)

Third, the configuration settings in Listing 3 will forbid access (response status code 403) if a parameter contains either “\x0a” (aka “newline” or “\n”) or “\x0d” (aka “carriage return” or “\r”). One or both of these control characters will be generated when the user presses the Enter key, depending on whether the user is on a Unix/Linux computer or a Windows computer.

Forbidding these characters as part of request parameters would be an appropriate response for most websites where forms contain simple input fields such as checkboxes, radio-buttons, and textfields. In fact, the common default response when a user presses the Enter key is to submit the form, not to put control characters in the field. If, however, the form contains a textarea element, then the user is allowed to enter multiple lines of text. Pressing Enter does not submit the form but places one or both of the control characters into the parameter value.

So for websites using textarea elements, an alternate approach is to not forbid these control characters but to allow and escape them. In other words, remove them from the configuration settings for the deny parameter in Listing 3, modify BadInputFilter to add a configuration boolean parameter escapeNewlines (which is similar to the other escape parameters), and then replace all occurrences of “\r” with a space character (“ “) and “\n” with the HTML “
” (or “
” for XHTML). This escape replacement should work fine for most Windows and Unix/Linux environments, including Apple OS/X. You would need to make one more small change in order for the modification to work, however.

It is important to be certain when escaping parameters that you escape the angle bracket (<) before you escape newlines, since the escape for newlines contains an angle bracket. But since all escape patterns in BadInputFilter are stored in a HashMap, you have no guarantee of the iteration order when retrieving the entries. The solution is twofold:

1. Use a LinkedHashMap rather than a HashMap; for field parameterEscapes; that is, in the code for BadInputFilter, replace the declaration:


protected HashMap<String, String> parameterEscapes = new HashMap<String, String>();

with


protected Map<String, String> parameterEscapes 
    = new LinkedHashMap<String, String>();

(Note that "LinkedHashMap<String, String>()" can be shortened to "LinkedHashMap<>()" in Java 7 and later.)

2. Ensure that the entries to escape newlines are added to the parameterEscapes Map after the entries to escape angle brackets. It is important to ensure that angle brackets are escaped first.

In conclusion

If you develop and/or maintain a website that is open to the public, you should anticipate that someone will try to hack into it at some point in time. One way to reduce your website’s vulnerability is by validating all user input. The class BadInputFilter was originally designed to provide a frontline of defense for several well-known web application security exploits by filtering requests and either forbidding the request or escaping the potentially malicious input. With more recent versions of Tomcat, part of the functionality provided by BadInputFilter no longer works. In this article I’ve described changes to BadInputFilter that will restore the lost functionality. Moreover, unlike the original version of BadInputFilter, the updated version does not depend on the implementation of Tomcat and should run correctly in any Servlet/JSP container.

John I. Moore, Jr., Professor of Mathematics and Computer Science at The Citadel, has a wide range of experience in both industry and academia, with specific expertise in the areas of object-oriented technology, software engineering, and applied mathematics. For more than three decades he has designed and developed software using relational databases and several high-order languages, and he has worked extensively in Java since version 1.1. In addition, he has developed and taught numerous academic courses and industrial seminars on advanced topics in computer science.

1 2 3 Page 2
Page 2 of 3