[Java] Override HTTP Request Parameters

Often in a Java web-application I come across cases where it would be useful to directly override or modify one or more HTTP request parameters. To be clear, by “request parameter” I am referring to the value that is returned by the ServletRequest‘s ‘getParameter()‘ method. For whatever reason the architects of the Servlet spec decided that direct modification of request parameters was not to be supported, despite the number of use-cases that can benefit from such a feature.

For example, say that you have a Filter that you are using to sanitize input parameters and guard against things like XSS attacks by ensuring that obviously invalid values like “<script>alert(‘hacked!’);</script>” are filtered out. Wouldn’t it be great if you could implement your Filter such that when it finds one or more forbidden values it flags the request as potentially malicious (using a request attribute), removes any parameters that contain potentially unsafe data, and relocates the potentially unsafe data to a predetermined quarantine area (also accessible via a request attribute)? This would protect your webapp code from ever receiving a malicious parameter, while still allowing parts of the code that might permit seemingly malicious parameters (for instance, there is no reason to prevent a user from registering with a password of “<script>alert(‘hacked!’);</script>” if that is what they want to use, particularly if you are hashing user passwords like you should be) to still access them if desired by going through the quarantine area.

Of course, two-thirds of the functionality described above can be implemented without being able to override request parameters. With the standard API you can certainly check for potentially malicious parameters, set an attribute if you find any, and copy their values into a quarantine area. But what you cannot do is remove the parameters from the request, so any application code that directly accesses a request parameter value may still be at risk, particularly if its author forgets to check to see if the request has been flagged as suspect. The real beauty of being able to override parameter values is that you can do things like completely prevent a malicious parameter from ever being visible to your application code unless your application code goes out of its way to look for it (and if you do that, and you do it incorrectly, then that’s your own fault).

Anyways, the code to enable this kind of functionality is a fairly straightforward (if tedious) exercise in writing an HttpServletRequest wrapper and then overriding a few choice methods (and adding a couple new ones):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class OverridableHttpRequest implements HttpServletRequest {

	private HttpServletRequest wrappedRequest;
	private Map<String, String> newParams;
	private Set<String> removedParams;

	public OverridableHttpRequest(HttpServletRequest requestToWrap) {
		this.wrappedRequest = requestToWrap;
		this.newParams = new HashMap<String, String>();
		this.removedParams = new HashSet<String>();
	}

	// these things we add so that params can be overridden
	public void setParameter(String name, String value) {
		this.removedParams.remove(name);
		this.newParams.put(name, value);
	}

	public void removeParameter(String name) {
		this.newParams.remove(name);
		this.removedParams.add(name);
	}

	// these things we need to override so that the correct state is exposed through the standard API
	@SuppressWarnings("rawtypes")
	@Override
	public Enumeration getParameterNames() {
		Set<String> result = new HashSet<String>();
		Enumeration requestParams = this.wrappedRequest.getParameterNames();
		while (requestParams.hasMoreElements()) {
			Object param = requestParams.nextElement();
			if (!removedParams.contains(param)) {
				result.add((String) param);
			}
		}
		result.addAll(newParams.keySet());

		return Collections.enumeration(result);
	}

	@Override
	public String[] getParameterValues(String arg0) {
		//NOTE:  not strictly to spec
		String[] result = new String[1];
		result[0] = this.getParameter(arg0);

		return result;
	}

	@Override
	public String getParameter(String arg0) {
		if (removedParams.contains(arg0)) {
			return null;
		}
		if (newParams.containsKey(arg0)) {
			return newParams.get(arg0);
		}
		return this.wrappedRequest.getParameter(arg0);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Map getParameterMap() {
		Map<String, String[]> result = new HashMap<String, String[]>();
		for (Object key : this.wrappedRequest.getParameterMap().keySet()) {
			result.put((String)key, (String[])this.wrappedRequest.getParameterMap().get(key));
		}
		for (String key : this.newParams.keySet()) {
			result.put(key, new String[] {this.newParams.get(key)});
		}
		for (String key : this.removedParams) {
			result.remove(key);
		}

		return result;
	}

	// these things we should probably override but don't right now
	@Override
	public String getRequestURI() {
		// FIXME: should return a modified URI based upon current state
		return this.wrappedRequest.getRequestURI();
	}

	@Override
	public StringBuffer getRequestURL() {
		// FIXME: should return a modified URL based upon current state
		return this.wrappedRequest.getRequestURL();
	}

	@Override
	public String getQueryString() {
		// FIXME: should return a modified String based upon current state
		return this.wrappedRequest.getQueryString();
	}

	// everything else just passes through
	@Override
	public Object getAttribute(String arg0) {
		return this.wrappedRequest.getAttribute(arg0);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Enumeration getAttributeNames() {
		return this.wrappedRequest.getAttributeNames();
	}

	@Override
	public String getCharacterEncoding() {
		return this.wrappedRequest.getCharacterEncoding();
	}

	@Override
	public int getContentLength() {
		return this.wrappedRequest.getContentLength();
	}

	@Override
	public String getContentType() {
		return this.wrappedRequest.getContentType();
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		return this.wrappedRequest.getInputStream();
	}

	@Override
	public String getLocalAddr() {
		return this.wrappedRequest.getLocalAddr();
	}

	@Override
	public String getLocalName() {
		return this.wrappedRequest.getLocalName();
	}

	@Override
	public int getLocalPort() {
		return this.wrappedRequest.getLocalPort();
	}

	@Override
	public Locale getLocale() {
		return this.wrappedRequest.getLocale();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Enumeration getLocales() {
		return this.wrappedRequest.getLocales();
	}

	@Override
	public String getProtocol() {
		return this.wrappedRequest.getProtocol();
	}

	@Override
	public BufferedReader getReader() throws IOException {
		return this.wrappedRequest.getReader();
	}

	@SuppressWarnings("deprecation")
	@Override
	public String getRealPath(String arg0) {
		return this.wrappedRequest.getRealPath(arg0);
	}

	@Override
	public String getRemoteAddr() {
		return this.wrappedRequest.getRemoteAddr();
	}

	@Override
	public String getRemoteHost() {
		return this.wrappedRequest.getRemoteHost();
	}

	@Override
	public int getRemotePort() {
		return this.wrappedRequest.getRemotePort();
	}

	@Override
	public RequestDispatcher getRequestDispatcher(String arg0) {
		return this.wrappedRequest.getRequestDispatcher(arg0);
	}

	@Override
	public String getScheme() {
		return this.wrappedRequest.getScheme();
	}

	@Override
	public String getServerName() {
		return this.wrappedRequest.getServerName();
	}

	@Override
	public int getServerPort() {
		return this.wrappedRequest.getServerPort();
	}

	@Override
	public boolean isSecure() {
		return this.wrappedRequest.isSecure();
	}

	@Override
	public void removeAttribute(String arg0) {
		this.wrappedRequest.removeAttribute(arg0);
	}

	@Override
	public void setAttribute(String arg0, Object arg1) {
		this.wrappedRequest.setAttribute(arg0, arg1);
	}

	@Override
	public void setCharacterEncoding(String arg0)
			throws UnsupportedEncodingException {
		this.wrappedRequest.setCharacterEncoding(arg0);
	}

	@Override
	public String getAuthType() {
		return this.wrappedRequest.getAuthType();
	}

	@Override
	public String getContextPath() {
		return this.wrappedRequest.getContextPath();
	}

	@Override
	public Cookie[] getCookies() {
		return this.wrappedRequest.getCookies();
	}

	@Override
	public long getDateHeader(String arg0) {
		return this.wrappedRequest.getDateHeader(arg0);
	}

	@Override
	public String getHeader(String arg0) {
		return this.wrappedRequest.getHeader(arg0);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Enumeration getHeaderNames() {
		return this.wrappedRequest.getHeaderNames();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Enumeration getHeaders(String arg0) {
		return this.wrappedRequest.getHeaders(arg0);
	}

	@Override
	public int getIntHeader(String arg0) {
		return this.wrappedRequest.getIntHeader(arg0);
	}

	@Override
	public String getMethod() {
		return this.wrappedRequest.getMethod();
	}

	@Override
	public String getPathInfo() {
		return this.wrappedRequest.getPathInfo();
	}

	@Override
	public String getPathTranslated() {
		return this.wrappedRequest.getPathTranslated();
	}

	@Override
	public String getRemoteUser() {
		return this.wrappedRequest.getRemoteUser();
	}

	@Override
	public String getRequestedSessionId() {
		return this.wrappedRequest.getRequestedSessionId();
	}

	@Override
	public String getServletPath() {
		return this.wrappedRequest.getServletPath();
	}

	@Override
	public HttpSession getSession() {
		return this.wrappedRequest.getSession();
	}

	@Override
	public HttpSession getSession(boolean arg0) {
		return this.wrappedRequest.getSession(arg0);
	}

	@Override
	public Principal getUserPrincipal() {
		return this.wrappedRequest.getUserPrincipal();
	}

	@Override
	public boolean isRequestedSessionIdFromCookie() {
		return this.wrappedRequest.isRequestedSessionIdFromCookie();
	}

	@Override
	public boolean isRequestedSessionIdFromURL() {
		return this.wrappedRequest.isRequestedSessionIdFromURL();
	}

	@SuppressWarnings("deprecation")
	@Override
	public boolean isRequestedSessionIdFromUrl() {
		return this.wrappedRequest.isRequestedSessionIdFromUrl();
	}

	@Override
	public boolean isRequestedSessionIdValid() {
		return this.wrappedRequest.isRequestedSessionIdValid();
	}

	@Override
	public boolean isUserInRole(String arg0) {
		return this.wrappedRequest.isUserInRole(arg0);
	}

}

So the new methods being added here are ‘removeParameter(String name)‘ and ‘setParameter(String name)‘, which do pretty much what their name implies. If you are familiar with the standard ‘removeAttribute(String name)‘ and ‘setAttribute(String name)‘ methods, then you should feel right at home with these new additions. They simply let you manipulate request parameters in a way that’s identical to how you can already manipulate request attributes.

One minor deviation from the Servlet specification that is worth noting is that I have overridden ‘getParameterValues(String name)‘ such that it only returns the first value associated with a given parameter. This means that if for some reason your webapp uses URL’s like “http://mysite.com/api?user=bob&user=jane&user=paul” then you will only see “bob” as a value for the ‘user’ parameter. In practice I have not ever come across a web application that intentionally relied on a single parameter name having multiple values associated with it, and if you are designing your web application in such a way then you should probably just stop and pick a less confusing pattern. I see no value in a feature that allows a single parameter to have multiple values that you only get to see if you use a different API method to get them (‘getParameterValues‘ instead of ‘getParameter‘), and so I have removed this feature from the implementation. If someone can come up with a solid justification for having such a feature, I will add it back in.

Also, left as an exercise is overriding ‘getRequestURI()‘, ‘getRequestURL()‘, and ‘getQueryString()‘ to return the correct values based upon the modified request state. It’s fairly rare to have application code that depends upon the values of these calls, so in most cases you will not need to do this.

In any case, to make use of the OverridableHttpRequest class, you can do the following:

public class ExampleFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		//do initialization things here
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws IOException, ServletException {

		HttpServletRequest httpReq = (HttpServletRequest) response;
		OverridableHttpRequest newRequest = new OverridableHttpRequest(httpReq);

		//do work and modify the request as desired
		newRequest.removeParameter("someBadXssParam");

		//pass the modified request on to the webapp, anyone downstream will see
		//the modified state with no 'someBadXssParam' in it
		filterChain.doFilter(newRequest, response);
	}

	@Override
	public void destroy() {
		//do shutdown things here
	}
}

Simple, but powerful. I just wish the Servlet spec included this kind of functionality out of the box so that it wouldn’t be necessary to implement a complete HttpServletRequest wrapper just to add a couple of basic mutator methods.

This entry was posted in coding, java and tagged , , . Bookmark the permalink.

One Response to [Java] Override HTTP Request Parameters

  1. chandra says:

    this looks good.but i want to process the newrequest to read that takes httpservletrequest……
    how can i try

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>