We have a J2EE website has been served for many of years, which use Tuckey UrlRewriteFilter as rewrite module to make url seo friendly.
Last week we found a XSS injection, here is what lesson learned.
Firstly let's involve UrlRewriteFilter.
UrlRewriteFilter uses java standard `java.util.regex` package to match regular expression and do redirect.
Create Pattern object
if (caseSensitive) {
pattern = Pattern.compile(patternStr);
} else {
pattern = Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE);
}
// put the remaining ending non-matched string
if (lastMatchEnd < from.length())
sb.append(from.substring(lastMatchEnd));
// (Similar to the source code prevoius version 3.2 org.tuckey.web.filters.urlrewrite.RuleBase
)
// get existing eg, $1 items
replacedTo = matcher.replaceAll(replacedTo);
For user just need to edit the rule in the xml with format
<rule>
<from>^/some/olddir/(.*)$</from>
<to>/very/newdir/$1</to>
</rule>
Our website has a seo redirect rule like:
Request URL format
/product/${sku no}/${sku name}/${something-else}
# Example: /product/123456/apple/?test
Redirect URL format
query?id=${sku no}&name=${sku name}
# Example: query?id=123456&name=apple
To meet the requirement, let's start with a simplest regular expression. Same to edit xml, let's debug in java code for more plain
String targetURL = "query?id=$1&name=$2";
String pattern1 = "^/product/(.*)/(.*)/.*";
String generalURL = "/product/123456/apple/?test";
Matcher m1 = Pattern.compile(pattern1, Pattern.CASE_INSENSITIVE).matcher(generalURL);
System.out.println(m1.replaceAll(targetURL));
Output:
query?id=123456&name=apple
See? We ok? NO!
Now append some character in the end of url
String exceptionURL = "/product/123456/apple/test/";
Matcher m2 = Pattern.compile(pattern1, Pattern.CASE_INSENSITIVE).matcher(exceptionURL);
System.out.println(m2.replaceAll(targetURL));
Output:
query?id=123456/apple&name=test
See that? Parameter id is incorrect. How to fix it?
Regular expression is matching greedy by default, disable here by character `?`
String pattern2 = "^/product/(.*?)/(.*?)/.*";
Matcher m3 = Pattern.compile(pattern2, Pattern.CASE_INSENSITIVE).matcher(exceptionURL);
System.out.println(m3.replaceAll(targetURL));
Output:
query?id=123456&name=apple
Looks good right? Are we ok now? NO!
Let's inject some script get agent's cookie
String xssURL = "/product/123456/apple/\n\t<script>alert(document.cookie)</script>";
Matcher m4 = Pattern.compile(pattern2, Pattern.CASE_INSENSITIVE).matcher(xssURL);
System.out.println(m4.replaceAll(targetURL));
Output:
query?id=123456&name=apple
<script>alert(document.cookie)</script>
The script has been saved raw. If you directly print the value on the page, your website will be under attack:
// access url
/product/123456/apple/%0A%09%0A%09%0A%09%0A%09/%3Cscript%3Ealert(document.cookie)%3C/script%3E
// output parameter to the page like
// <link rel="alternate" href="${some parameter}" />
Generally enable DOTALL will solve this problem
Matcher m5 = Pattern.compile(pattern2, Pattern.DOTALL).matcher(xssURL);
System.out.println(m5.replaceAll(targetURL));
Output: query?id=123456&name=apple
However UrlRewriteFilter hard coded the flag to `CASE_INSENSITIVE` as source code shows above.
Fortunately there still is a way to enable dotall mode embedded by "(?s)"
String pattern3 = "^/product/(?s)(.*?)/(.*?)/.*";
Matcher m6 = Pattern.compile(pattern3, Pattern.CASE_INSENSITIVE).matcher(xssURL);
System.out.println(m6.replaceAll(targetURL));
// Output: query?id=123456&name=apple
OK then the problem solved, any XSS script will be blocked.
Do not use regular expression.
Encode any character to the end.
Use modern framework.
09 Oct 2014