CVE-2014-7808 - Apache Wicket CSRF (2014)

This is about a vulnerability I discovered in Apache Wicket in 2014, but never got around to publishing my write-up. So it's kinda outdated now...
Apache Wicket is a web application framework for Java and is used by quite a few big sites. I had a closer look at the encrypted url feature, which supposedly protects from cross-site request forgery.

Unfortunately the proposed simple example is inherently flawed for two reasons. First I will give a quick reminder what CSRF (cross-site request forgery) is - you can skip over it if you are familiar with that term. Then I will explain why this solution doesn't protect you from CSRF and at the end I will propose a solution that works.

CSRF Introduction

Cross-site request forgery is very simple but powerful. Imagine a browsergame with a form to send gold to another user:

When you submit this form, the browser will send a GET request to http://www.example.com/send_gold?gold=9999&user=samuirai.
Now wouldn't it be great if all players of the game would be so nice to send you all their gold for showing them what CSRF is?

Just embed this URL as a picture, for example in your game profile or on a fan site:

<imgsrc="http://www.example.com/send_gold?gold=9999&user=samuirai">

← Hint: Open the developer console of your browser go to the Network tab and reload this site.

Every player who is logged into the game and visist a site with this image will unwillingly send this request to the game server and transfer the gold to you.

Defeating Encrypted URLs

Apache Wicket had the great idea that encrypted URLs stop an attacker from doing this, presumably because an attacker can't guess the URL.
The default implementation org.apache.wicket.util.crypt.SunJceCrypt uses CRYPT_METHOD = "PBEWithMD5AndDES";, which means a password is hashed with MD5 (with a salt and 17 rounds) and this hash is used as key and iv for DES - not a very strong method, but there are bigger problems.

But apache wicket does two mistakes here. First mistake is that the example implementation uses the default password: WiCkEt-FRAMEwork. Many many sites don't bother or don't know they should change the password. So an attacker can easily decrypt the URLs and generate all the valid URLs he wants - not only for CSRF but also for other attacks such as reflected XSS (how convinient that the URL hides injected Javascript from XSS auditor and alert users :P).

Proof of concept: This python script will try to decrypt URLs using a standard password. pip install pycrypto required.

Ok let's assume the developers knew about the default password and changed it to sUp3r-pw. And nobody has a fast brute-force implementation for PBEWithMD5AndDES. They are still vulnerable to CSRF. How? - Well I as an attacker really don't care about the content of the URL. I just want to know where I can send the request to.

So this means the example "secure" implementation doesn't protect from CSRF at all.

Defeating Stateful URLs

Actually a bigger obstacle than encrypted URLs are Apache Wickets stateful URLs. They are easily identified with the number as parameter such as ?2:

http://www.example.com/?2

Form URLs typically look like this:

http://www.example.com/send_gold?2-3

Basically the first number is always incremented while visiting different subsites. While the second number is incremented on multiple refreshes on a single page. So this actually makes guessing the URL more difficult. I as an attacker don't know at what number a user currently is.

This even makes the encrypted URLs look more "cryptic" (constantly changing):

Solutions

The best CSRF protection is a so called csrf-token. The server generates a random string for each form and embeds it as <input type="hidden" name="csrf-token" value="r4nd0m123">. When the form is submitted, the server verifies the token.

When using encryption, Apache Wicket should be configured to use org.apache.wicket.util.crypt.KeyInSessionSunJceCryptFactory which doesn't take a fixed key, but generates a new key for each user.

This info should also be added in the standard Apache Wicket Guide. Otherwise developers will continue to implement the default insecure example.

Funny sidenote: This master thesis analyzed the security of this feature and got it wrong.