I'm writing servlet-based application in which I need to handle the XSS vernability. I've implemented following logic to sanitize input using ESAPI and JSOUP library. For each request I am getting form plain text form parameter. I just want to sanitize it. If malicious content found then throw the exception otherwise continue the request flow.
public class XSSRequestWrapper extends HttpServletRequestWrapper{
public XSSRequestWrapper(HttpServletRequest request){
super(requrst);
}
@Override
public String[] getParameterValues(String parameter)
{
String[] values= super.getParameterValues(parameter);
int count = values.length;
for(int i=0; i<couny;i++){
sanitizeXSS(values[i]);
}
return values;
}
@Override
public String getParameter(String value)
{
value=super.getParameter(value);
sanitizeXSS(values);
return value;
}
private String sanitizeXSS(String input){
String esapiValue=ESAPI.encoder().conocalize(input, false, false);
esapiValue=esapiValue.replaceAll("\0","");
String unSanitizedStr=Jsoup.clean(esapiValue,Safelist.simpleText());
unSanitizedStr=Parser.unescapeEntities(sanitizedStr,false);
//Comparing above values to find out the xss vulnerability
if(esapiValue!= null || unSanitized!=null
||!esapiValue.equalIgnoreCase(unSanitizedStr)){
throw new RuntimeException("Found malicious content in the user input");
}
return input;
}
}
Above code snippet is working fine for all the opening closing tag like
- <script>alert()</script>
- <div>....</div>
- <script>malicious data...
etc etc but its failing for below payload
- ";alert('XSS');//
- window.alert("XSS');
For testing purpose I'm using payload from https://github.com/payloadbox/xss-payload-list How to solve this issue ?
What context are you presenting the result in? From your use of
unescapeEntitiesI am guessing that you are presenting this as plain text -- e.g. in a text email body? Or you have another layer in presentation that is re-encoding HTML entities before presenting in HTML? The context matters and could impact what steps are required.A string like
";alert('XSS');//is potentially dangerous if used unescaped in a HTML attribute.My suggestion would be - simplify the flow and clarify if the output of the function is meant to be plain text or HTML. If it's plain text, I would do something like:
And then the output is cleaned and safe for use in plain text contexts; and if you want to present it in HTML, encode any entities (using e.g. your HTML templating engine).
This pattern doesn't really make sense to me:
As the result of
.clean()is HTML and you are then unEscaping. Just skip that double step and use one of the.text()methods instead.After your edit, it's still not clear to me what content your input is (HTML, or?), and what context you want to display the output in.
I would break down the decision tree as:
1: if your input is HTML, and you want to keep it as HTML and make it safe, use the jsoup HTML Cleaner. You can optionally control what tags and attributes to preserve. The output is HTML. 2: if your input is HTML, but you only want the text content: use a text() method (and if the output context is a HTML body, escape it in your presentation layer) 3: otherwise, if your input is just text, don't do anything on input, and escape it if neccesarry on output.
If you are using multiple methods (like in your original example of using ESAPI, then jsoup HTML cleaning and keeping only textnodes, then unescaping and converting that from HTML to plain text) -- I feel your problem statement or solution design is under-specified and it needs a re-think. I would expect to see only one step, as outlined in the list before.
Or, you need to more crisply define what is "malicious". In your earlier example (which you removed on edit), the strings which were emitted were not dangerous if using in an HTML body context or a plain-text context. That one of the methods changed the input string doesn't make it necessarily malicious, IMV. But you could define what you consider an attack (vs just a string getting escaped or whatever) and additionally scan for that. I.e, consider two distinct passes: one over the input to decide if you think there's an "attack", and a second (which always need to run, regardless of the output of the earlier) to just normalize and sanitize the input, following the decision tree I mentioned above.