Introduction
We recommend keeping the code at hand as you look through this write-up, to get a better feeling for the vulnerable code patterns. Also, take the time to re-read the code if you forgot its workings. If you didn’t compete in the challenge, try to see how many bugs you can find before reading on. The original briefing for this challenge (and the code) can be found here.
We thank all contestants for their hard work. It was rewarding to see so many security minded people take the time to make an effort. Findings, attack strategies and recommendations ranged enormously, beautifully revealing the diverse ways one can review code.
More than 20 contestants entered the Spot The Bug challenge and the deadline for submitting reports has passed. Based on the number of findings, the reports and the recommendations, we've picked a top 3:
- Cernica Ionut
- William Breuer
- Siu Siu Ha
Congratulations Cernica! You’ve earned the Parrot AR. Drone 2.0 Elite Edition, and it’s being sent your way.
The bugs
Basic application flow
The application contains a simple login mechanism. Logging in can be done by supplying a username, a password and a ‘nonce’. If the login is successful, a session token is generated with the nonce and the username. The username is appended to the session token. The session token is then stored in a cookie. The browser must always send the cookie and a nonce in the URL. To verify if the session is valid, the server recalculates the token with the username and the nonce. If the session token is valid, the user is greeted with his user data.
Authentication and authorization bypasses
“What do you mean, why's it got to be built? It's a bypass. You've got to build bypasses.”
1. Retrieving other users' data
If a user logs in or has a valid session token, the user’s data is retrieved using the method uf.getUserData
.
68. String username = request.getParameter("user");
[...]
78. if (verifySession(ses, nonce))
88. {
89. message = "Welcome " + ses.split("-")[1];
90. data = uf.getUserData(username);
The session token contains the username, and is used together with the nonce to generate the token for validation. To set the “Welcome” message, the username from the validated token is used. But the username for retrieving user data is derived from the user
parameter in the URL! An attacker with any valid session can retrieve user data for any user by changing the user
parameter. The programmer should have used the username from the validated session token.
2. Spoofing a successful login using the nonce
The nonce submitted by the user is matched against a regular expression. If there is no match, an error message is generated that contains the nonce. This error message is stored in the variable message
.
When a login is successful or a valid session token is provided, the string “Welcome” is added to the message
variable. Line 116 contains an if statement that checks if the variable message
contains the string "Welcome". If it does, the application assumes the user is logged in.
82. if (!uf.isNullOrEmpty(nonce) && nonce.matches("([a-zA-Z]+)*"))
83. message = "Nonce must be a number, not " + nonce;
[...]
116. if (message.contains("Welcome"))
This check can be bypassed by supplying a nonce that contains "Welcome” and some characters that don’t match the regular expression. The generated error message will contain the nonce and thus the string “Welcome”, making the application think the user supplied a valid session.
3. Possible bypass due to insecure pattern matching
The checkLogin
method is used to check if a login attempt is successful. The username and password provided by the user are sent to a SOAP service. Code for the SOAP service is not provided, but what we do know is that the response from the server is validated against a regular expression. This expression checks if the response from the server contains the string “Authorized”.
13.public Boolean checkLogin(String username, String password)
14.{
15. String loginResult = uf.getLoginSOAPXML(username,password);
16. return loginResult.matches("^.*Authorized.*$");
17.}
Any response from the SOAP service containing the string "Authorized" will make the application think the user logged in successfully. A few possible bypasses may be possible here. For example, if the server would return “Not Authorized”, the method will return true. Or if the username “Authorized” is provided and the service would respond with "User Authorized doesn't exist”, the login would be bypassed. Other possibilities are stack traces that contain “Authorized”, etc. Wildcard matching in such a way poses a security risk and should never be used in real applications.
Code execution
4. Spring Expression Language Injection (ELI)
The tag spring:message
is used for displaying the username. The username
variable comes from a URL parameter, and can contain any value.
118. Welcome <spring:message text="<%= username %>" /> <br>
JSP Expression Language can be used for handy in-line JSP magic. This can come in handy for programmers. For example, the code “${param.user}” will evaluate to the user
parameter in the URL.
The EL in the text
attribute of the spring:message
tag will be evaluated. But in some versions of Spring this value is evaluated twice! This means that the username provided by the user will also be evaluated as Expression Language. By passing EL in the username, it’s possible to retrieve or set all sorts or values. For example, is the username is “${uf.secret}”, we might get back the secret used by the application. In some cases of ELI Remote Code Execution can be achieved by using input such as “${java.lang.Runtime.getCurrentRuntime().exec(“..”)}”..
Denial of service
The next two vulnerabilities are partially caused by an integer overflow in the getSquare
method. This method will square the nonce from the user and return it.
36. public Long getSquare(String nonce)
37. {
38. long n = Long.parseLong(nonce);
39. long squared = 0;
40. for(int i = 0; i != (n * Math.abs(n)); i++)
41. {
42. if (i < 0) i = 0;
43. squared += 1;
44. }
45. return squared;
46. }
5. Infinite loop when nonce is negative
What if our nonce is negative? Notice the use of Math.abs. If the nonce is -5
, then (n * Math.abs(n))* = *-5 * 5* = *-25
. The integer i
starts at 0 and will increase until it reaches n*Match.abs(n
. If you’re not familiar with overflows you might think i
can never become negative if we keep increasing it.
If i = 0
, and we keep increasing i
, can i
every become negative? In many programming languages - including Java - this is the case. An integer like i
can have a value from -2^31 to 2^31-1. If an integer has the value 2^31-1 and we increase it by one, the counter will continue at -2^31. This is called an integer overflow.
So if n
is negative, i
will continue counting until an overflow happens. But because of the code “if (i < 0) i = 0;”
, i
will become positive as soon as an overflow occurs. Thus the application will be caught in a infinite loop. A possible ‘patch’ for this issue would be to remove this if statement to allow i
to become negative. However, this would not fix our following finding.
6. Infinite loop when nonce is greater than sqrt(maxint)
Longs can hold much larger values than integers. In our code, n
is a long and i
is an integer. As explained in the previous finding, if i
becomes greater than 2^31-1, it will continue negative. So, if we can get n*Math.abs(n)
to become greater than 2^31-1, the loop will continue infinitely. If the nonce n
is greater than sqrt(maxint) = sqrt(2147483647) = 46340
, i
will never reach this value so it'll be stuck in an infinite loop.
7. Hanging a regular expression
The regular expression (regex) on line 82 checks if the nonce of a user contains lower or uppercase alphabet characters.
82. if (!uf.isNullOrEmpty(nonce) && nonce.matches("([a-zA-Z]+)*"))
If we translate this regex to a sentence, it would be something like “Find zero or more occurrences of one or more occurrences of lower or uppercase alphabet characters”. Such an expression can be tricked to evaluate in exponential time.
Let’s say our input is the string “aaa”. Our regex will go through the string to find that all characters match the expression (“This string contains one occurrence of three occurrences of the letter a”). Great. But if our string is “aaa!”, our regex will first go through all the characters only to find that “This string contains one occurrence of three occurrences of the letter a, but also an invalid character”. Now it will continue to find all combinations of two consecutive a’s, then all combinations of single a