Predicting a “Random” Number

Predicting a “Random” Number

Have you ever looked at a code snippet that creates for example a password reset token or something, uses a non-cryptographically secure number generator, and wondered: “How can this actually be exploited?”.

Well, me too!

In this post, I will show you a simple example of how to guess a non-cryptographically secure number, using like 20 lines of code.

So… what is a random number generator? Let’s ask ChatGPT!

Okay, this might be an oversimplified version. In its essence, a random number generator generates a sequence of numbers. In order to get a different sequence of numbers every time (so that it appears random), we need to input a value into the function to mix things up. This value is called the “seed”. For insecure random functions (such as 'Math.random()' in JavaScript, 'random' in Python, or 'java.util.Random' in Java), the seed is typically something predictable such as the current time.

If you know the seed used to generate a random number, then you can pass it into the generator and get as output the same number again. This means that random numbers can often trivially be guessed if the seed is predictable.

How can we Attack this then?

Say we have a little Ruby app that has a password reset functionality, where users can request a password reset link while providing a user name. This then sends an email with the form 'https://our-app.com/reset-password/<token>'. To generate the token, we use the built-in 'Random' function (which is not actually random!), and create a snippet like this:

require 'time'

# Seed the random number generator with the current time
seed = Time.now.to_i
random_generator = Random.new(seed)

# Generate the token
token = random_generator.rand        

So what does this mean now?

It means that if an attacker knows the seed, they would be able to predict the token. And what is the seed? The current time. Aha. But we don’t know the time the token was created, do we?

Well, we do! As an attacker, we would request a password reset for the admin user ourselves, and then try to bruteforce the token. For this, we would note the exact time that we request the token, and then run a bruteforcing script a couple of seconds later using all values in the timespan from the moment of requesting the token to now, as seed values. Because… One of these has to be the seed used to generate the admin’s password reset link!

For this, we would use a Ruby script like this:

require 'time'

def brute_force_secret()
    # Go back 10 seconds and use all numbers from 10 sec ago till now as seeds
    (Time.now.to_i - 10).upto(Time.now.to_i) do |seed|
        random_generator = Random.new(seed)

        # Try to visit https://our-app.com/reset-password/<token> and
        # reset the admin's password using the token
        
        # [...] This is omitted here for brevity
        
    end

    # If we were unable to reset the admin's password
    return false
end        

We then simply run the script and let it create all possible tokens for our time span, and try to reset the admin’s password using every single one of them. And one of them will be correct because the seed was the current time and we roughly know the time of the token creation and thus, the seed being used!

You can find a small PoC for breaking Ruby’s Random on my GitHub: https://github.com/dub-flow/random-bruteforce.


How to Remediate this?

By using a CSPRNG. A what?

That’s a mouthful of an acronym, ae? It stands for “Cryptographically secure pseudorandom number generator”, or, in easy words, a number that is actually random (or at least as random as it can get — nothing in life is fully random).

As a seed, these use crazy things like the noise of your network card. Also, they have mechanisms in place that even if you use the same seed twice, you won’t get the same sequence of random numbers every time.

Examples of secure random number generators are 'SecureRandom' in Java 'crypto.randomBytes' in Node.js.

Ok.. So Why the Heck did I choose Ruby?

Great question! Well, first of all, I don’t have a lot of Ruby experience and I love checking out different languages. But there is of course a deeper reason, which is that Ruby’s built-in random function is perfect to showcase bruteforcing insecure random number generators.

Initially, I wanted to use JavaScript’s 'Math.random()' for this until I realized that it was not suitable for my demonstration. The reason is that you cannot pass a seed to 'Math.random()' which means I can’t do the simple use cases of “trigger the random number generation and then bruteforce all numbers, using all possible seeds from the last 10 seconds”.

Another candidate I considered was Python’s 'random'. But, while it can be manually seeded, it uses the Mersenne Twister algorithm, which is more complex and less predictable, making a bruteforce demonstration more challenging and less straightforward.

Ruby was perfect because it allows explicit seeding with known values like the current time, making its output predictable and reproducible, which is key for demonstrating the bruteforcing here.

Closing Thoughts

I hope this brief introduction to random and non-cryptographically secure number generators has helped you gain a better understanding of why it’s vital to use a CSPRNG for any random number that is security-sensitive.

If you have any questions or thoughts about this post, feel free to reach out to me.

Robin Long

Founder at Kiowa Security

6 个月

This is cool ??. Lol it's true of course that a good RNG could perfectly well give you one day 314159265359. Perhaps just to annoy you.

Cody Bell

Cybersecurity Engineer

6 个月

Nicely done! Interesting concept and article does a great job explaining it

Ronald Hernández

Offensive Security Engineer | AppSec | OSWE | eWPTX | eJPT | CEH | CTF Player |

6 个月

That's one of my favorite vuln, a few days ago I found in a Java app the use of random to generate a temporary password, as I had the time in the same server response I could guess the password.

Nathaniel Shere

Delivering hands-on learning in the most secure way | Product Security Engineer at Skillable, where people learn by doing

6 个月

  • 该图片无替代文字
Daniel Aagren Seehartrai Madsen

ServiceNow | Software Engineering | Ethical Hacking | Architecture

6 个月

bool isRandomNumber(int number) { ?return number == rand.Next(); }

要查看或添加评论,请登录

Florian Walter的更多文章

社区洞察

其他会员也浏览了