Java – Cracking the Random: CVE-2024-29868
TL;DR
If you employ a Java application with a token-based password recovery mechanism, be sure that said token isn’t generated using: RandomStringUtils
.
Spoiler: You can crack it and predict all past and future tokens generated by the application!
Some Context
During a Penetration Test I was sifting through the internet – as one often does – looking for ways to do some damage in a Java application. I identified that the app in question was built using JHipster, a framework I was unfamiliar with at the time. Between the many articles and blog posts, I found an interesting vulnerability affecting this framework that could lead to Admin account takeover. I read some more and found out that the vulnerability had been reported by Jonathan Leitschuh. An illumination struck. I recalled seeing his presentation at No Hat in Bergamo, where he discussed a major vulnerability he helped mitigate through numerous GitHub pull requests. Jonathan’s security advisory referenced a talk by Alejo Popovici titled “The Java Soothsayer,” presented at EkoParty Argentina in September 2017, where he detailed a cryptographic flaw and included a proof of concept for exploiting it. Intrigued by this information, I delved deeper into the specifics of the vulnerability. Although the application I was testing turned out not to be vulnerable, I was determined to see if I could find other applications that might be.
I inspected various GitHub repositories, focusing on those that met the following criteria:
- Utilized the RandomStringUtils class for token generation.
- Included a user registration functionality.
- Implemented a token-based password recovery mechanism.
Meet Apache StreamPipes
Through some research on GitHub, I found several Apache projects that met the first requirement. The one that satisfied all three – and that I ended up focusing on – was Apache StreamPipes.
From the website:
“Apache StreamPipes is a self-service Industrial IoT toolbox to enable non-technical users to connect, analyze and explore IoT data streams.”
By analyzing the code one can see that, until v0.93
, the file streampipes-model/src/main/java/org/apache/streampipes/model/util/ElementIdGenerator.java
contains the following method to generate recovery tokens:
public static String makeRecoveryToken() { return RandomStringUtils.randomAlphanumeric(40); }
The documentation for the RandomStringUtils
class mentions that:
“Instances of Random, upon which the implementation of this class relies, are not cryptographically secure”.
I had everything I needed. I then proceeded to spin up a test environment with Docker.
NB: If you want to follow along and reproduce this vulnerability you can find the instructions in the following repository: https://github.com/DEVisions/CVE-2024-29868
Exploiting The Vulnerability
Once the application was up and running I needed to make sure that the “Allow self-registration” and “Allow self-service password recovery” options were checked.
After that, I selected the option to create a new account.
I registered with an e-mail that would be caught by MailHog.
I then went back to the login page and selected the “Forgot password” option.
And asked for a password recovery token for the user I just registered.
Through this process, I obtained two vital pieces of information: the specific path the application uses to reset a user’s password and a valid token required to complete the process.
The next step involved cloning the repository available on GitHub containing the proof of concept (POC) developed by Alejo Popovici.
Once done, I compiled it with the following command (I got some warnings but it compiled correctly):
gcc -o cracker crack.c -lpthred -03 -m64
-lpthread
: Links the pthread library, which is necessary for multithreading support.-O3
: Enables the highest level of optimization, which can improve the performance of the program.-m64
: Is used to generate code for a 64-bit environment.
I used the output binary to crack the token I previously received in my attacker mail inbox.
I did the cracking on my laptop inside a VM so performance could have been better, but in the end, the time it took was reasonable (1 hour and 50 minutes. By using a more powerful machine the time required would be drastically reduced).
This way I obtained the possible 100 future tokens that the application would generate.
Finally, I asked for a password reset token for the administrator account.
Lo and behold, the password reset token sent by the application to the administrator e-mail address is the same as the underlined one of the previously predicted tokens.
We now have proof that by cracking a valid password reset token, we can set a new password for the administrator account, effectively taking it over.
With an Intruder session it would be a matter of seconds to find the valid endpoint, even if in the meantime other password recovery tokens were to be asked.
Vulnerability Detection
To detect both an Apache StreamPipes installation and vulnerability CVE-2024-29868 there are two Project Discovery Nuclei templates in the previously linked repository.
Attack Infographic
Understanding The Impact
While the attack path detailed in this article provides a specific example on how this type of vulnerability can be exploited, it’s important to understand that the same underlying principles can be applied to a wide range of attack vectors. For instance, similar methods can be used to exploit weaknesses involving cookies or any other randomly generated strings within an application. By manipulating these elements, attackers can gain unauthorized access, steal sensitive data, or compromise the integrity of the system. Therefore, it’s crucial that any application employing the RandomStringUtils
class is doing so only to generate non-security related strings.
More About Random String Generation
When developing applications, generating random strings is often necessary. When using Java, the Apache Commons Lang 3 library provides a handy utility for this: the RandomStringUtils
class. This class includes the method randomAlphanumeric(int)
, which generates a random alphanumeric string of the specified int
length.
The randomAlphanumeric(int)
method is straightforward:
String randomStr = RandomStringUtils.randomAlphanumeric(10); System.out.println(randomStr); // Outputs a random 10-character string, e.g., "aB3dEfG9H1"
Generating these strings involves pseudorandom number generation, as true randomness is hard to achieve with a computer. Java uses the java.util.Random
class for this purpose. This class implements a linear congruential generator (LCG), which produces sequences that appear random but are determined by an initial seed.
- Seed Initialization: The pseudorandom number generator (PRNG) starts with a seed. If not specified, it often defaults to the current time.
- Number Generation: It uses a mathematical formula to generate the next number in the sequence, using the current seed.
- Repeat: This process repeats, generating a sequence of pseudorandom numbers.
Why It’s Not Cryptographically Secure
While RandomStringUtils.randomAlphanumeric(int)
is useful for many purposes, it is not suitable for security-sensitive tasks – like generating password recovery tokens – for the following reasons:
- Predictability: The
java.util.Random
class is predictable if the seed is known. Since the same seed generates the same sequence of numbers, attackers could potentially predict future values if they discover the seed. - Lack of Entropy: The default seed may not provide enough randomness, making it easier for attackers to guess.
- Not Designed for Security: LCGs are not designed to resist cryptographic attacks. They are meant for simulations and non-critical applications where predictability is not a concern.
Suitable Alternatives
For cryptographically secure random values, a better alternative is java.security.SecureRandom
.
SecureRandom
provides a much higher level of randomness and unpredictability, making it suitable for generating secure tokens, passwords, and other sensitive data.
Here’s a basic implementation example that could be used to generate a cryptographically secure random password recovery token:
SecureRandom secureRandom = new SecureRandom(); byte[] token = new byte[byteLength]; secureRandom.nextBytes(token); String secureToken = Base64.getUrlEncoder().withoutPadding().encodeToString(token); System.out.println(secureToken); // Outputs a secure random token
Following is a breakdown of the code:
SecureRandom secureRandom = new SecureRandom();
First, the snippet creates an instance of SecureRandom
. Unlike java.util.Random
, SecureRandom
is designed to generate cryptographically strong random values, making it suitable for security-sensitive applications.
byte[] token = new byte[byteLength];
Then, a byte array with a certain length (here expressed by byteLength
, which is an integer) is defined. The length of the array determines the size of the token. For example, if you need a 32-character string, you use 24 bytes.
secureRandom.nextBytes(token);
Next, the token
byte array is filled with random bytes. The nextBytes
method uses the SecureRandom
instance to generate the random bytes, ensuring they are cryptographically secure.
String secureToken = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
Finally, the byte array is converted into a URL-safe Base64 encoded string. Base64 encoding is a common way to represent binary data in an ASCII string format, which is useful for including in URLs or other text-based protocols. The withoutPadding
method removes any trailing =
characters, making the string more compact (but make sure that the consuming systems can handle unpadded Base64 encoded strings!).
Events Timeline
06/09/2023 – Initial contact with the Apache Security Team to inform them about the possible vulnerability. Same day reply from Apache asking for further analysis.
28/11/2023 – Follow up from Apache StreamPipes sharing the pull request that should have addressed the problem.
02/12/2023 – Follow up on my part after conducting some further analysis with a working POC exploiting the vulnerability.
07/12/2023 – Acknowledgement of the vulnerability by the Apache StreamPipes team.
19/12/2023 – Follow up on my part asking for an expected timeframe for the release of the fix.
24/01/2024 – Reply from Apache StreamPipes developers saying that it would be present in the next release expected for February.
21/03/2024 – Record creation date of CVE-2024-29868
08/04/2024 – New follow-up from Apache StreamPipes sharing the PR addressing the vulnerability.
09/04/2024 – Follow-up on my part informing that the issue wasn’t entirely resolved. Reply from Apache StreamPipes team issuing a new fix.
03/06/2024 – Follow-up from Apache StreamPipes team informing that the release process for the fixed version had begun.
13/06/2024 – Release of the fixed version.
22/06/2024 – Vulnerability disclosure by Apache Software Foundation.
25/06/2024 – POC publication.
Resources & References
- https://lists.apache.org/thread/zqn5z48gz7bp0q8ctk96ht8bc7vd3njv
- https://www.cve.org/CVERecord?id=CVE-2024-29868
- https://github.com/DEVisions/CVE-2024-29868
- https://github.com/alex91ar/randomstringutils/tree/master
- https://github.com/jhipster/generator-jhipster/issues/10401
- https://cwe.mitre.org/data/definitions/338.html
- https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/RandomStringUtils.html
- https://docs.oracle.com/javase/8/docs/api/java/util/Random.html
Author
Alessandro Albani is a member of Yarix Red Team. He is a tech enthusiast who loves fiddling with electronic gadgets. When not behind a screen, he’s likely jamming out to his favorite tunes or juggling one of his many other hobbies (more than can be reasonably listed).