Vesta Admin Takeover: Exploiting Reduced Seed Entropy in bash $RANDOM

Vesta is a web-based control panel that offers a streamlined and user-friendly interface for managing Linux servers. Similar to platforms like cPanel and Plesk, Vesta simplifies the process of hosting websites, managing domains, creating databases, etc. It is designed for ease of use, providing a minimalistic approach that focuses on the most essential features required for server management. Vesta is particularly favored for its lightweight structure, which ensures fast performance even on lower-spec servers. It supports a wide range of Linux distributions and is an excellent choice for users looking for a cost-effective and reliable server management solution. In this blog post we will present a Vesta admin takeover case study by exploiting reduced seed entropy in bash $RANDOM. $RANDOM is a built-in Bash environment variable that generates a pseudo-random integer between 0 and 32767 each time it is referenced and is used sometimes for password or token generation.

The Password Reset process

We always enjoy reviewing the password reset mechanism of every CMS, because it’s unauthenticated attack surface. This case proved to be no exception and it allowed us to create an exploit for Vesta admin takeover. Everytime you try to reset your password you’ll receive an email like the following, where you need to have the code parameter in order to perform the password reset. This code is pre-generated: it’s first generated on Vesta installation time and then every time you reset your password it will use the previous code and generate a new code for the next password reset.

The way Vesta is structured is as a PHP API which calls various bash scripts in the /bin folder that do the heavy lifting, and the password reset functionality follows this exact pattern. First let’s have a look at the PHP script which does the password reset.

This file checks if the reset token($_POST[‘code’]) received from the url is equal to $rkey and if they match it will proceed to reset the user’s password.

$rkey is the existing password reset token and you can find it in the configuration file for each user. Every time a user resets his password, he will receive an email with a link containing this token. Here’s an example of this from /data/users/admin/user.conf

Vesta  - User configuration file

As you saw above the PHP file executes v-change-user-password bash script, so let’s have a look at it:

In lines 57-58, a new reset token ($RKEY) is generated and the new password is saved. Let’s have a look at the generate_password function:

Vesta admin takeover - insecure generate_password bash function

bashrandomcracker to crack $RANDOM

The function is pretty straightforward, it uses the bash $RANDOM environment variable and the modulo operator to generate an index within the matrix string in order to create a password or a reset token. As you’ve probably guessed this $RANDOM variable is not crypto secure, but how do we actually exploit it? We need to understand how $RANDOM generates random numbers and this github project is a good starting point. You can give it as input a few $RANDOM numbers and it can guess the seed that was used to generate them and also predict the next 3 $RANDOM numbers:

$ bashrand crack -n 3 $RANDOM $RANDOM $RANDOM
Seed: 2137070299 +3 (old)             # Seed found
  Next 3 values: [22404, 16453, 2365] # predicting the next random numbers
$ echo $RANDOM $RANDOM $RANDOM
22404 16453 2365 # generating next 3 $RANDOM and they match with the 3 above

or given a seed it can generate the next random numbers.

$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM
24879 21848 15683
$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM
24879 21848 15683

We’ve used this project as a starting point to build our POC. In a nutshell if we brute force the seed, we’ll be able to generate all the password reset tokens. What is the seed’s type though and what are its min and max values? For this we need to dive into the bash internals, let’s have a look at the relevant functions:

The sbrand() function in bash sets the seed for random number generation. As you see from the above screenshot, “seed” is an “unsigned long” so its limits are 0 to ~4.3Billion. Knowing this we tried a few ideas:

  • brute force all 4.3 Billion seeds – a terrible idea, this exploit would take weeks to execute
  • get some sort of information leak to reduce the brute force scope. Some endpoints seemed to expose useful timestamps in guessing the seed, but we couldn’t find a way to abuse them unauthenticated. A useful timestamp is the timestamp of the last password reset.

The Breakthrough – reduced seed entropy

At this point, I was pretty much out of ideas, especially after I couldn’t find any useful information leaks. I started looking through the PHP internals(newer & older versions) for some ideas and noticed this:

Vesta Admin Takeover - sample seeding from PHP core

Do you notice how the PHP devs are seeding in the lcg_seed function? It is using the current timestamp (tv.sec) and microseconds (tv.tv_usec), but there’s also a left bitshift, Why? By shifting tv.tv_usec to the left by 11 positions you ensure that the top bits of tv_sec will also be modified, not just the lower bits, because tv_usec needs 20 bits (max is 1000000). If you don’t do this left shift, the upper bits are static and as we know we can get timestamp from HTTP response headers. Let’s have another look at our seeding function in bash and see how many issues there are with it.

Bash seedrand – limitations

The code once again:

Vesta Admin Takeover - Diving into bash source code

And the issues are the following:

  • tv.tv_sec – the current timestamp and occupies 8 bytes, but uses only 4 bytes in practice. You can store timestamps up to year 2038 on just 4 bytes.
  • tv.tv_usec – the microseconds and occupies 8 bytes, but uses 20 bits in practice (there’s 1.000.000 microseconds in a second and 20 bits is enough to store this)
  • getpid() – the process pid and occupies 4 bytes and the max value we’ve seen in our tests was around 660000, which needs 20 bits*.
  • Thus, the XOR operation will only change the lower 20 bits. There’s no bit shifting here, unlike in the PHP core. This was the “AHA” moment.

NOTE: getpid() could be the only deal breaker here, but it would have to be a really high number to break our exploit. This would happen on a system running for a very long time or if there is a fork() bomb.

By only changing the lower 20 bits of the current timestamp, we reduce entropy, and the seed will fall within an interval of approximately 12 days around the current timestamp. As shown below, we calculate the minimum (with the lowest 20 bits set to 0) and the maximum (with the lowest 20 bits set to all 1’s). It should be clear that the timestamp is the only factor that matters here, and the PID of the process and the microseconds are irrelevant.

<?php
$timestamp = time();
echo "Original Timestamp: " . $timestamp . "n";
echo "Orig Date: " . date('y-m-d H:i:s', $timestamp) . "n";

// Create a mask where the lowest 20 bits are 0
echo "nn";
$mask = ~((1 << 20) - 1);
$modifiedTimestamp = $timestamp & $mask;

echo "Mask 20 bits - set to 0n";
echo "Modified Timestamp: " . $modifiedTimestamp . "n";
echo "Timestamp in Binary: " . decbin($modifiedTimestamp) . "n";
echo "Modified Date: " . date('y-m-d H:i:s', $modifiedTimestamp) . "n";
echo "nn";

// Create a mask where the lowest 20 bits are 1
echo "Mask 20 bits - set to 1n";
$end = $timestamp | ((1 << 20) - 1);
echo "end Timestamp: " . $end . "n";
echo "end Date: " . date('y-m-d H:i:s', $end) . "n";
echo "end Timestamp in Binary: " . decbin($end) . "n";
?>

So, when the password reset happens, your seed for token generation would be within this range for a given timestamp:

Vesta Admin Takeover – “Local” exploit

With this information at hand we can create a much more practical exploit than having to brute-force 4B values. The strategy that we propose for this exploit is to brute force the timestamps backwards year by year to find our seed. We consider a reasonable assumption that the last password reset (or the VESTA installation) was 1-2 years ago. Before creating the actual web exploit we’ve extended BashRandomCracker to test our theory locally. We’ve brute forced all the seeds for the current token (rust is fast) and we found that the seed was indeed within the expected range.

Vesta Admin Takeover - testing the exploit locally

We got the above token from the local user.conf file for testing purposes:

Vesta Admin Takeover - local exploit

Vesta Admin Takeover – Turbo Intruder to the rescue

To brute force for the last year [2023 – 2024] we start at $one_year_ago = time()-(86400*365) and we stop at $end=time(). This makes it a total of 31.5M attempts per year (86400 * 365) and it requires 25 bits, which is more than the 20 bits of entropy introduced by microseconds and getpid. If we haven’t found the seed, we’ll go back another year [2022-2023]. We’ve created a Turbo Intruder script which is here. How much did we actually optimize with this? Assuming we brute force the last 3 years (~95M requests), which is a bad scenario, that is a ~98% reduction of requests from a total of 4.2 Billion. For optimization tips for turbo intruder, please see our exploit for Concrete CMS.

We hope you have enjoyed our write-up on the Vesta admin takeover, diving into PHP internals, Bash internals, and exploring a bit of C and Rust along the way.

Vesta Admin Takeover - Turbo Intruder script

See Our Leading Research Insights

For those interested in exploring more security research similar to our study on mobile app vulnerabilities, consider these insightful articles:

Moreover, explore these resources to deepen your understanding of security testing and stay updated on best practices in the field.

Conclusion

Firstly, as we continually advance our tools and techniques for effective web application security testing, we invite you to explore our web application pentesting services. Indeed, at FORTBRIDGE, we take pride in being a leading pentesting provider with a team of senior consultants who have a proven track record in the industry. Moreover, our experts use advanced methodologies and tools to deliver comprehensive, actionable insights tailored to your specific needs. Specifically, we have the Dubai Electronic Security Center (DESC) and the Council of Registered Ethical Security Testers (CREST) accredit our services, and we design them to fortify your mobile applications and ensure the highest quality of security testing.

Furthermore, dive into the latest and most effective security strategies with our expertly crafted solutions. To that end, for detailed information about our offerings or to discuss customized penetration testing strategies, please visit our services page or contact us today. In conclusion, with FORTBRIDGE, you can be confident in our commitment to safeguarding your organization’s systems and data, helping you stay ahead of evolving cyber threats.

Timeline

22/06/24 Vulnerability reported to the vendor
26/06/24 no answer from the vendor, we asked for an update
04/07/24 no answer from the vendor, we followed-up again
22/07/24 another follow-up with the vendor
30/07/24 another follow-up with the vendor
22/09/24 3 months have passed since initial disclosure
02/10/24 blog post published

References

https://github.com/fortbridge/BashRandomCracker
https://github.com/fortbridge/BashRandomCracker
https://portswigger.net/research/turbo-intruder-embracing-the-billion-request-attack
Reflections from BlueHat IL 2025 – where this research was presented

About Post Author