Now that we have narrowed the scope of what we need to look through, we need to pin down the file that creates these tokens. After a little digging, I found that the creation of the tokens happens in ./com/sun/faces/renderkit/ServerSideStateHelper.java. Let’s dive into there and see what that looks like.

The value of the javax.faces.ViewState object that is written to the page is the result of the writeState method in ServerSideStateHelper.java. This method calls createRandomId() on lines 213 and 223, and these values are concatenated together on line 234. This is where things get interesting. createRandomId() is defined on line 498. All it does is return a Java Long that is derived from the random object in this file. The random object was declared on line 133 and instantiated on line 150. The object itself is part of the java.util.Random class which was imported on line 48.

An important take away from that brief dive into the source is that the ViewState value is generated using Java’s Random class. After taking a look at Java’s documentation we can see that there is a disclaimer that reminds the user about the dangers of using the Random class.

Some research into this bug led me to find out that the reason Java’s Random class is considered insecure is because it makes use of a Linear Congruential Generator (LCG) to generate random numbers. The clearest discussion I found was in this blog.

In Oracle’s documentation for Random we see that the constructor that was called on line 150 of ServerSideStateHelper.java actually makes a call to Random rnd = new Random(); and rnd.setSeed(seed); under the hood, and the call to nextLong() is actually two calls to next() with some shifting and casting ((long)next(32) << 32) + next(32);

Now we know how the tokens are generated, and we know that the tokens are not cryptographically random. As a result, we should be able to create our own tokens now. If we can create our own tokens, we should be able to carry out a CSRF attack.

Will We be Able to Hack It?

Based on what we read in the blog I mentioned above by James Roper, all I need is x2 Ints to figure out the seed used to generate the numbers used for the ViewState. After looking at Java’s documentation on nextLong we figured out that the token that is embedded in the page is composed of x4 Ints because it is x2 Longs separated by a colon `:` and calls to Random.nextLong() return the sum of two calls to Random.next(32).

For our test, we ought to be able to use the first Long that was generated to figure out the value of the second Long in the ViewState token for any page in the application. As an adversary, we would like to carry out our attack from the perspective of an unauthenticated user to reduce the amount of access we will need for our campaign. Preferably, there would be a page that has one of these tokens and is exposed to everyone on the Internet. For our example, we are going to suppose the vulnerable application has a login page with a ViewState token on the login form.

Now comes the fun part, we need to figure out the seed used for the LCG. Below you can see a summary of what you need to know for now. We create a Random object called rnd, we then call setSeed on the object which updates the seed value for that object. Then, we call nextLong which in turn calls next(32) internally updating the seed value and then shifting the return value of next(32) to the left by 32 bit positions so that it occupies the higher end of a Long, and doing the same thing again without the shift to occupy the lower 32 bit positions of a Long.

After we have a good understanding of what happens during the random number generation process, we will be able to understand how to reverse this process and extract the seed.

Calculating new ViewStates (Source Code Walkthrough)

/*PART 1*/

Taking it back to our known ViewState value in the login page, we will first need to extract the Longs and break them into more manageable Ints. To split the x2 Longs into x4 Ints we can use the code in the snippet illustrated in Part 1 of the github gist below.

/*PART 2*/

After we have our test Ints, the process of finding the seed is fairly simple. When next(32) is called, the seed is updated and shifted the right by 16 bit positions. The result of that right shift is the random number that is returned to the user. If we were to shift the generated number to the left by 16, there are only 2¹⁶(65536) possibilities for the remaining bits. Thus, we can figure out our seed by adding the result of our left shift to every number between (0, 65536) and generating a new number until we end up with the same value as the second number that was already generated for us previously by the LCG. In Part 2 of the github gist below I illustrate what this process would look like. (Note: I practically pulled this out of James Roper’s blog post. I reference his blog above, but here it is again.)

/*PART 3*/

Now that we have computed the seed, we will need to calculate the values used for the idInActualMap value we extracted from the login page, updating the state of the random number generator. This is illustrated in Part 3 of the github gist below.

/*PART 4*/

Last, we need to calculate the value of the new ViewState object we are going to use for our CSRF attack. This is going to be the same as the process of updating the state of the random number generator, except that we are going to need to store these values to return them. I illustrate this process in Part 4 of the github gist below.

What next?

We can hook our ViewState cracker into an HTML form generator so that we can carry out our Cross-Site Request Forgery (CSRF) attack. The attacker will need to spin-up a web-server that accesses the publicly facing endpoint that contains the ViewState parameter, and pass it to the ViewState cracker. It will also need to “prime” the targeted endpoint by tricking the user into issuing a GET request so that there are idInLogicalMap and idInActualMap values for that page.

Take aways:

It is important to ensure that applications are sufficiently secured against attacks by vetting their security measures thoroughly. I learned early on in my career as a security consultant that assumptions are what get you hacked, it is important to both understand the technology that is being used and understand how it is being used. Javax Faces ViewState parameters were not designed as CSRF tokens and they should not be used as such. CSRF tokens ought to be cryptographically secure random numbers in order for to successfully prevent attacks such as the ones I illustrated in this blog. Also, this is not a “vulnerability” in this library as ViewState tokens do not claim to be cryptographically secure.