3

Let me preface this by saying this is my first attempt to understand input sanitation, and at the moment I'm still a newbie to PHP. I've put together a quick register/log in website to test some things, and I'm having trouble breaking it. I'm trying to exploit my own website so that I can figure out (by myself, hopefully) how to fix this.

I've done quite a bit of research, but no matter how many different things I try I simply cannot affect my site. What I'm trying to do is get anything done, from dropping tables to getting user info - anything that could pose a risk. Below is my stripped down code. I'm quite sure that there is space for injection, but I honestly can't find it. Also, pay no mind to my use of md5. I know that there are a better things out there!

Assume that $user and $password have come from a form with nothing done to it.

$password = md5($password);

$query = mysqli_query($con,"SELECT * FROM users WHERE name='$user'");
$numrows = mysqli_num_rows($query);

if ($numrows == 1){

    $row = mysqli_fetch_assoc($query);
    $dbpass = $row['password'];
    $dbuser = $row['name'];

    if ($password == $dbpass){  
        $_SESSION['name'] = $dbuser;
    }
}

What I'm looking for is any exploit that I can use to learn from. If you have tips on how to secure it, that would be appreciated as well.

Cheers.

  • 4
    Separate from your question, you really shouldn't be using md5 for passwords. PHP has `password_hash()` for this purpose. [Official guide](http://php.net/manual/en/faq.passwords.php) – Aron Foster Apr 07 '15 at 19:54
  • @Anonymous I don't think that it's really a duplicate. The question title is only a bit misleading, it shouldn't be `Am I free from SQL Injection?`, but `How to exploit this SQL injection?` – tim Apr 07 '15 at 20:53
  • @tim If you look at the first answer to that question, the example answer this question quite well. Sure, the question as a whole is different, but it can effectively be referred to as a canonical question. (Otherwise, it wouldn't be allowed for the question to be so broad.) The answer on that question even literally uses the exact same exploit as one of the answers here does. – Anonymous Apr 07 '15 at 20:56
  • @Anonymous you mean `' or 1=1 --`? But that doesn't work in this case, because it would result in `$numrows == 1` being false (`$password == $dbpass` would be false as well). Another way to phrase this question might be `how to exploit SQL injection in login page that doesn't echo data, and also can't be bypassed via ' or 1=1 -- (because the password comparison takes place outside of the query)`, but that would be a rather long title. – tim Apr 07 '15 at 21:03
  • @tim You are absolutely right. My mistake. Still, if that query is actually being executed, there are some serious problems. That particular example probably would not do much, but I would be stunned if nothing else from the numerous online exploits could use the same premise to exploit a vulnerability. Plus, to be technical, the question specifically asks to get *anything* done. Executing slow queries could be something. I'm not saying the question is definitely a duplicate, but it's certainly not a separate issue. – Anonymous Apr 07 '15 at 21:10
  • 1
    @Anonymous I think _anything_ means anything interesting (ie, not only causing an error). A slow query would be something, but I'm not sure how it is slow? Using this injection to aid in a DOS on the other hand is interesting (and now that you mention it, [DOS via SQL injection](http://www.bryanavery.co.uk/file.axd?file=2013%2F10%2FDoS+Attacks+Using+SQL+Wiltcards.pdf) would also be possible here). – tim Apr 07 '15 at 21:17

2 Answers2

4

Have you tried including quotes to break this? From what I see here, it should actually cause a horrible error if a quote is passed in. Sometimes you have to fiddle around a little before the crash happens.

$query = mysqli_query($con,"SELECT * FROM users WHERE name='$user'");

Here is my attempt to break this:

'OR 1=1--

It appears you have not used parametrized queries, therefore leaving your code open to injection. Note the above is just a proof of concept - with your current code it wont actually let you do much that you can see.

Your code validates the results of the query after it is executed, which is very dangerous. You are open to SQL timing attacks at the very least.

Instructions for implementing Parametrized queries are below: http://php.net/manual/en/mysqli.prepare.php https://www.owasp.org/index.php/Query_Parameterization_Cheat_Sheet

Additionally:

1)As a beginner you are much better off using a pre-made ORM.

2)Input sanitization is key. Always do this before the data gets into a query.

3)If you are doing this to learn security, try a pre-made war game of a similar ilk - it may give better guidance for learning to exploit the bug.

Disclaimer: I don't know this particular PHP API too well, so if it's doing parametrized queries by default, I couldn't tell or was not aware of it.

MrSynAckSter
  • 2,020
  • 10
  • 16
3

Yes, you are completely open to SQL injection, eg via validUsername' injectionPayload %23, which would give you this query (the # (encoded as %23 cuts of the remaining '):

SELECT * FROM users WHERE name='validUsername' injectionPayload #'

Into Outfile

depending on the rights of the user, you could write content into a file with this payload:

-1' UNION ALL SELECT 'injected string' INTO OUTFILE '/path/to/file.php' #

Blind Injection (with valid user credentials)

With your current code, it's hard to get any data directly from the database. You only use the password and the name, and both of those have to be correct, as otherwise your code will not work.

But this is a login script, so it will create a different result for valid user credentials and for invalid ones, correct?

As that is the case, standard blind SQL injection can be used (if you have valid user credentials). The payloads would look something like this:

// mysql 4 or 5?
validUsername' AND substring(version(), 1, 1)='5
validUsername' AND substring(version(), 1, 1)='4

// is first char of database hash smaller than ascii 100?
validUsername' AND ascii(substring((SELECT password FROM mysql.user LIMIT 0,1),1,1))<100 #
[...]

And so on. What happens is that you combine the query with a boolean question. Eg does the database version start with a 5?. If the answer is true, then everything works normally, and you are logged in. If the answer is false, you will not be logged in, as the query will not return data, and thus $numrows == 1 is false.

It creates a lot of requests (especially for a login script), but it is possible to extract data.

Login Bypass

It should also be possible to bypass the login page (login as any user if you know the username but not the password):

-1' UNION ALL SELECT name, md5('password') FROM users WHERE name='attackedUser' #

I didn't test this, but what should happen: the -1 removes the legitimate results of your query, the union adds the results of the injected query, and the injected query gets the name of an attacked user, and sets the password to password for this request (and you submit password as the POST value for password).

Blind Injection (without valid user credentials)

As we have seen in the section above, we can actually set the password, so we do not need valid credentials for a blind injection. You could either combine the first and second attacker described, or I could imagine something like this:

-1' UNION ALL SELECT 1,md5(user) FROM mysql.user LIMIT 0,1 #

This is just a simplified string to show the idea: again, you remove the valid data with -1, you add your query, and you select as the password the data you want to extract (md5 hashed, as the supplied password will be hashed). Then, as the password you submit via POST, you submit the value you guess (eg root). Those two values will then be compared via $password == $dbpass. If you are logged in, your guess is correct, if not, then it's wrong. For guessing password hashes, you can use the substring approach from above.

Conclusion

Even if you think that the SQL injection you have doesn't disclose any information, you should still use prepared statements, as there might be some way to disclose information. And even if there isn't right now, your code might change in the future.

tim
  • 29,018
  • 7
  • 95
  • 119