Luis Ashurei

Thinking in Data

| | email

Hows website with tuckey UrlRewriteFilter leak XSS injection

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.

UrlRewriteFilter

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);
}

Matching and replace

// 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>

URL Format

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

Example A

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!

Example B

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?

Example C

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!

Example D

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}" />

Example E

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.

Example F

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.

In Conclusion

Do not use regular expression.
Encode any character to the end.
Use modern framework.

09 Oct 2014