XSS filter

I tested the code below against all of the known xss attacks that are listed on http://ha.ckers.org/xss.html as of July 2007, except for one. I haven't yet tested it against the 'US-ASCII encoding' attack (which tomcat is vulnerable)

The wrapper escapes all params that Stripes binds during its Validation & Binding phase.  Parameters that you get manually through request.getParameter() are NOT sanitized. The code below basically follows the xss security guidance posted at http://www.owasp.org.

Notes:
*This init-param goes in the stripesfilter section
*If you have Interceptor.Classes listed already, I believe this should be listed as the last param value in the existing list.
*This example requires jregex jar to run successfully (jregex.sourceforge.net)

add to web.xml. for example:
 
<init-param> 
<param-name>Interceptor.Classes</param-name> 
<param-value> 
com.mypath.web.security.XssInterceptor 
net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor
</param-value> 
</init-param> 

XssInterceptor.java
 
package com.mypath.web.security; 

import javax.servlet.http.HttpServletRequest; 

import net.sourceforge.stripes.action.Resolution; 
import net.sourceforge.stripes.controller.ExecutionContext; 
import net.sourceforge.stripes.controller.Interceptor; 
import net.sourceforge.stripes.controller.Intercepts; 
import net.sourceforge.stripes.controller.LifecycleStage; 
import net.sourceforge.stripes.controller.StripesRequestWrapper; 

@Intercepts(LifecycleStage.BindingAndValidation) 
public class XssInterceptor implements Interceptor 
{ 
private static ThreadLocal<ExecutionContext> currentContext = new ThreadLocal<ExecutionContext>(); 

public Resolution intercept(ExecutionContext context) throws Exception 
{ 
StripesRequestWrapper stripesWrapper = null; 
HttpServletRequest originalRequest = null; 
try { 
currentContext.set(context); 
stripesWrapper = StripesRequestWrapper.findStripesWrapper(context 
.getActionBeanContext().getRequest()); 
originalRequest = (HttpServletRequest) stripesWrapper.getRequest(); 
stripesWrapper.setRequest(new XssRequestWrapper(originalRequest)); 
return context.proceed(); 
} finally { 
currentContext.remove(); 
if (stripesWrapper != null && originalRequest != null) 
stripesWrapper.setRequest(originalRequest); 
} 
} 
} 

XssRequestWrapper.java
 
package com.mypath.web.security; 

import java.util.HashMap; 
import java.util.Map; 
import java.util.Set; 

import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletRequestWrapper; 

import org.apache.log4j.Logger; 

public class XssRequestWrapper extends HttpServletRequestWrapper 
{ 
private static final Logger logger = Logger.getLogger(XssRequestWrapper.class); 


private Map<String, String[]> sanitized; 
private Map<String, String[]> orig; 

@SuppressWarnings("unchecked") 
public XssRequestWrapper(HttpServletRequest req) 
{ 
super(req); 
orig = req.getParameterMap();	
sanitized = getParameterMap(); 
if (logger.isDebugEnabled()) 
snzLogger(); 
}	

@Override 
public String getParameter(String name) 
{	
String[] vals = getParameterMap().get(name); 
if (vals != null && vals.length > 0) 
return vals[0]; 
else 
return null; 
} 


@SuppressWarnings("unchecked") 
@Override 
public Map<String, String[]> getParameterMap() 
{	
if (sanitized==null) 
sanitized = sanitizeParamMap(orig); 
return sanitized;	

} 

@Override 
public String[] getParameterValues(String name) 
{	
return getParameterMap().get(name); 
} 


private Map<String, String[]> sanitizeParamMap(Map<String, String[]> raw) 
{	
Map<String, String[]> res = new HashMap<String, String[]>(); 
if (raw==null) 
return res; 

for (String key : (Set<String>) raw.keySet()) 
{	
String[] rawVals = raw.get(key); 
String[] snzVals = new String[rawVals.length]; 
for (int i=0; i < rawVals.length; i++) 
{ 
snzVals[i] = SafeHtmlUtil.sanitize(rawVals[i]); 
} 
res.put(key, snzVals); 
}	
return res; 
} 


@SuppressWarnings("unchecked") 
private void snzLogger() 
{ 
for (String key : (Set<String>) orig.keySet()) 
{ 
String[] rawVals = orig.get(key); 
String[] snzVals = sanitized.get(key); 
if (rawVals !=null && rawVals.length>0) 
{ 
for (int i=0; i < rawVals.length; i++) 
{ 
if (rawVals[i].equals(snzVals[i]))	
logger.debug("Sanitization. Param seems safe: " + key + "[" + i + "]=" + snzVals[i]);	
else 
logger.debug("Sanitization. Param modified: " + key + "[" + i + "]=" + snzVals[i]); 
}	
} 
} 
} 

// TODO need to sanitize getHeader(), getCookie(), etc ?? 

} 
SafeHtmlUtil.java (barebones version)
 
public class SafeHtmlUtil 
{ 
public static String sanitize(String raw) 
{ 
if (raw==null || raw.length()==0) 
return raw; 

return HTMLEntityEncode(canonicalize(raw)); 
} 


private static Pattern scriptPattern = new Pattern("script", REFlags.IGNORE_CASE); 
private static Replacer scriptReplacer = scriptPattern.replacer("&#x73;cript"); 

public static String HTMLEntityEncode(String input) 
{ 
String next = scriptReplacer.replace(input); 

StringBuffer sb = new StringBuffer(); 
for ( int i = 0; i < next.length(); ++i ) 
{ 
char ch = next.charAt( i ); 

if (ch=='<') 
sb.append("&lt;"); 
else if (ch=='>') 
sb.append("&gt;"); 
else 
sb.append(ch); 
} 

return sb.toString(); 
} 


// "Simplifies input to its simplest form to make encoding tricks more difficult" 
// though it didn't do seem to do anything to hex or html encoded characters... *shrug* maybe for unicode? 
public static String canonicalize( String input ) 
{ 
String canonical = sun.text.Normalizer.normalize( input, Normalizer.DECOMP, 0 ); 
return canonical; 
}