Broken User Authentication

Today, we’re going to dive into one of the biggest headaches in the API world: Broken User Authentication.
Picture this: you’re building the next big thing, a revolutionary API that will change the world as we know it. You’re coding away, making sure everything works perfectly, when all of a sudden, you realize something’s not quite right. Your users can access sensitive data they shouldn’t be able to!
Uh-oh. You’ve fallen victim to the dreaded broken user authentication. Don’t worry, it happens to the best of us. In this post, we’ll take a closer look at what exactly broken user authentication is, why it’s such a big deal and most importantly, how to fix it. So sit back, relax and grab a cup of tea/coffee, because it’s time to learn how to secure your API once and for all.

Purpose of the blog post

The purpose of the blog is to help API developers and security experts understand broken user authentication in API security, why it’s a problem and what they can do to protect their API from security breaches.

Scenario

Protecting sensitive information and ensuring user privacy requires a secure user authentication system. In this scenario, let’s imagine that you are a developer building a web-based platform for a financial institution. The platform allows users to view their account information, make transactions and access other sensitive information.
You choose to put in place a user authentication system to make sure the platform is secure. To access the platform, users must enter their username and password into to the authentication system.
The PHP code for the user authentication system is demonstrated here:

<?php
session_start();

if (isset($_POST['submit'])) {
  $username = $_POST['username'];
  $password = $_POST['password'];
  
  $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
  $result = mysqli_query($conn, $sql);
  
  if (mysqli_num_rows($result) > 0) {
    $_SESSION['user_id'] = $username;
    header('Location: dashboard.php');
    exit;
  } else {
    $error = "Incorrect username or password";
  }
}
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
  <input type="text" name="username" placeholder="Username">
  <input type="password" name="password" placeholder="Password">
  <input type="submit" name="submit" value="Login">
</form>

In this scenario, the user authentication system is secure and properly implemented. The code uses a SQL query to verify the username and password entered by the user against the database of registered users. If the username and password match the user is logged in and redirected to the dashboard. If the username and password do not match an error message is displayed.
However, if the user authentication system were broken an attacker might be able to access private data by bypassing the authentication procedure. For example, an attacker could insert malicious code into the SQL query to access the user’s account if the code was susceptible to SQL injection attacks.
Because of this, it’s crucial to establish a secure user authentication system and to monitor and test it frequently for vulnerabilities. Organizations must take the required actions to safeguard sensitive information and maintain the privacy of their users.

Some Example

Broken User Authentication in API security refers to a vulnerability that allows unauthorized access to sensitive data in an API. This vulnerability can occur when the API does not properly validate user credentials or does not properly enforce access controls.
Let’s look at an example to see how broken user authentication can occur.

Lack of Input Validation

Consider the following PHP code for logging in a user:

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = $_POST["username"];
$password = $_POST["password"];

// Check if the user exists in the database
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

// If the user exists, log them in
if ($user) {
    $_SESSION["user_id"] = $user["id"];
    header("Location: /dashboard");
    die();
} else {
    header("Location: /login?error=1");
    die();
}

?>

In this example, the code is vulnerable to SQL injection attacks. An attacker could enter a username such as ‘ OR ‘1’=’1 and a password such as ‘ OR ‘1’=’1 which would result in the following query being executed:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '' OR '1'='1'

This query would return all rows in the users table, allowing the attacker to log in as any user without providing a correct password.
To prevent this vulnerability: the code should validate and sanitize the input data before using it in a query. For example, using the mysqli_real_escape_string function:

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = mysqli_real_escape_string($conn, $_POST["username"]);
$password = mysqli_real_escape_string($conn, $_POST["password"]);

// Check if the user exists in the database
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

// If the user exists, log them in
if ($user) {
    $_SESSION["user_id"] = $user["id"];
    header("Location: /dashboard");
    die();
} else {
    header("Location: /login?error=1");
    die();
}

?>

Storing Passwords in Cleartext

Consider the following PHP code for creating a new user:

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = $_POST["username"];
$password = $_POST["password"];

// Insert the new user into the database
$query = "INSERT INTO users (username, password) VALUES ('$username', '$password')";
mysqli_query($conn, $query);

?>

This code stores the user’s password in the database in cleartext which means that if the database is compromised, the attacker can see the passwords of all users. To mitigate this risk passwords should be stored securely using a password hashing function such as password_hash.

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = $_POST["username"];
$password = $_POST["password"];

// Hash the password
$password_hash = password_hash($password, PASSWORD_DEFAULT);

// Insert the new user into the database
$query = "INSERT INTO users (username, password) VALUES ('$username', '$password_hash')";
mysqli_query($conn, $query);

?>

Using a Predictable Session ID

Consider the following PHP code for logging in a user:

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = $_POST["username"];
$password = $_POST["password"];

// Check if the user exists in the database
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

// If the user exists, log them in
if ($user) {
    session_start();
    $_SESSION["user_id"] = $user["id"];
    header("Location: /dashboard");
    die();
} else {
    header("Location: /login?error=1");
    die();
}

?>

Due to the improper generation of the session ID, this code is vulnerable to session hijacking. Another user’s session ID might be predicted by an attacker, who could then use it to access into their account. ‘session create id‘ or another secure random number generator should be used to create the session ID in order to reduce this risk.

<?php

// Connect to the database
$conn = mysqli_connect("localhost", "user", "password", "database");

// Get the username and password from the form
$username = $_POST["username"];
$password = $_POST["password"];

// Check if the user exists in the database
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

// If the user exists, create a secure predictable session ID
if ($user) {
  // Generate a random string for the session ID
  $session_id = bin2hex(random_bytes(32));

  // Update the user's session ID in the database
  $update_query = "UPDATE users SET session_id = '$session_id' WHERE id = {$user['id']}";
  mysqli_query($conn, $update_query);

  // Set the session ID cookie with the HttpOnly and Secure flags
  setcookie("session_id", $session_id, 0, "/", "", true, true);

  // Redirect the user to the dashboard
  header("Location: dashboard.php");
  exit();
} else {
  // If the user doesn't exist, show an error message
  echo "Invalid username or password";
}
?>

In this code, we generate a random string for the session ID using the `random_bytes()` function and then store it in the database along with the user’s information. We also set the session ID cookie with the setcookie() function, which includes the HttpOnly and Secure flags to prevent cookie theft and session hijacking. By using a secure predictable session ID, we can help prevent attackers from hijacking a user’s session and accessing sensitive information.

Why is Broken User Authentication a Problem?

Broken user authentication systems can have serious consequences for both users and organizations. User authentication is the process of verifying the identity of a user who is attempting to access a platform or system. When this process is broken, it can result in several security incidents; such authentication system is broken, it can result in a variety of security incidents, such as:

Account Takeovers

Attackers can gain unauthorized access to user accounts by exploiting vulnerabilities in the authentication process. This can result in sensitive information being stolen such as credit card numbers, or unauthorized purchases being made on the user’s behalf.
Code example:

if(isset($_POST['username']) && isset($_POST['password'])){
  $username = $_POST['username'];
  $password = $_POST['password'];

  $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
  $result = mysqli_query($connection, $query);
  if(mysqli_num_rows($result) > 0){
    $user = mysqli_fetch_assoc($result);
    $_SESSION['user_id'] = $user['id'];
    header('Location: dashboard.php');
    exit;
  } else {
    $error = "Incorrect username or password";
  }
}

Due to this code’s vulnerability to SQL injection attacks, an attacker could be able to access user accounts without authorization. To ensure that user input is properly escaped, utilize prepared statements with parameterized queries as a remedy.

Session Hijacking

Attackers can take over a user’s session by predicting or stealing their session ID. This can give the attacker access to the user’s account and sensitive information.
Example Code:

session_start();

if(isset($_SESSION['user_id'])){
  $user_id = $_SESSION['user_id'];
  $query = "SELECT * FROM users WHERE id = '$user_id'";
  $result = mysqli_query($connection, $query);
  $user = mysqli_fetch_assoc($result);
} else {
  header('Location: login.php');
  exit;
}

This code is vulnerable to session fixation attacks, where an attacker can predict or steal the session ID allowing them to take over the user’s session. The solution to this is to regenerate the session ID after a successful login.

Password Reuse

Attackers can obtain user passwords using a data breach and use those passwords to acquire entry to the e-commerce platform. This can result in the attacker being able to make unauthorized purchases and access sensitive data. Code Example:

if (isset($_POST['submit'])) {
  $username = $_POST['username'];
  $password = $_POST['password'];
  
  $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
  $result = mysqli_query($conn, $sql);
  
  if (mysqli_num_rows($result) > 0) {
    $_SESSION['user_id'] = $username;
    header('Location: dashboard.php');
    exit;
  } else {
    $error = "Incorrect username or password";
  }
}

This code is vulnerable to password reuse attacks as the passwords are stored in cleartext and can be easily obtained in the event of a data breach. If a user reuses the same password for multiple accounts this password will be compromised and the attacker can access all of the user’s accounts.

RealLife Example

Cross-Site Scripting (XSS) Attack

Cross-Site Scripting attacks occur when an attacker injects malicious code into a web page allowing the attacker to steal sensitive information or perform other malicious actions.
Here is an example of an XSS attack in PHP:

<?php
$username = $_POST['username'];

echo "Welcome, $username";
?>

In this example, the user’s input is directly echoed without being properly sanitized making it vulnerable to XSS attacks. To prevent XSS attacks, user input should be properly sanitized and validated before being echoed to the page.

Insufficient Logout Functionality

Insufficient logout functionality occurs when a user is not properly logged out of a system, allowing an attacker to access sensitive information or perform malicious actions.
Here is an example of insufficient logout functionality in PHP:

<?php
session_start();

unset($_SESSION['user_id']);

header('Location: login.php');
exit;
?>

In this example; the user’s session is only unset not destroyed. This means that the session data is still stored on the server, making it vulnerable to attacks. To properly log out a user, the session should be destroyed using the ‘session_destroy‘ function.

Inadequate Password Strength Requirements

Inadequate password strength requirements occur when a system does not enforce strong password policies, allowing users to choose weak and easily guessable passwords.
Here is an example of inadequate password strength requirements in PHP:

<?php
$password = $_POST['password'];

$sql = "INSERT INTO users (password) VALUES ('$password')";
mysqli_query($conn, $sql);
?>

In this example; the password is inserted into the database without any checks for password strength. To enforce strong password policies the system should check the length and complexity of the password and require a minimum length and combination of characters like uppercase letters, lowercase letters, numbers and symbols.

Insecure Password Storage

One of the most common examples of broken user authentication is insecure password storage. This occurs when passwords are stored in plaintext or hashed without a salt which makes them vulnerable to attacks such as dictionary attacks or rainbow table attacks.
Here is an illustration of insecure password storage in PHP;

<?php
$username = $_POST['username'];
$password = $_POST['password'];

$sql = "INSERT INTO users (username, password) VALUES ('$username', '$password')";
mysqli_query($conn, $sql);
?>

In this example, the user’s password is stored in plaintext in the database, making it vulnerable to attacks. To secure the password, it should be hashed with a secure hashing algorithm such as bcrypt or Argon2 and salted before being stored in the database.

Session Fixation Attack

Session fixation attacks occur when an attacker is able to set or predict the session ID of a user, allowing the attacker to hijack the user’s session and access sensitive information.
Here is an example of a session fixation attack in PHP;

<?php
session_start();

$_SESSION['user_id'] = $_GET['user_id'];

header('Location: dashboard.php');
exit;
?>

In this example, the user’s session ID is set through a GET parameter making it vulnerable to session fixation attacks. To prevent session fixation attacks, session IDs should be regenerated after each successful login and session IDs should not be passed through GET parameters.

How to Fix Broken User Authentication

Fixing BUA requires a multi-step approach to secure the authentication process and protect sensitive user information.
Here are some best practices for fixing broken user authentication:

Store Passwords Securely

Passwords should be stored securely using a secure hash function, such as bcrypt or Argon2, to protect against password cracking attacks.
Here is an example; of how to store passwords securely in PHP using the bcrypt library:

<?php
// Hash the password using bcrypt
$password = password_hash($_POST['password'], PASSWORD_BCRYPT);

// Store the hashed password in the database
$sql = "INSERT INTO users (password) VALUES ('$password')";
mysqli_query($conn, $sql);
?>

Implement Proper Logout Functionality

To properly log out a user the session should be destroyed using the “session_destroy()” function. Here is an example, of proper logout functionality in PHP:

<?php
session_start();

// Destroy the session data
session_destroy();

// Redirect the user to the login page
header('Location: login.php');
exit;
?>

Use Encryption for Sensitive Data

Encryption is the process of converting plain text into ciphertext, which is unreadable without a decryption key. When storing sensitive data such as passwords or credit card information it is important to use encryption to protect against potential breaches. Here is an example of how to use encryption in PHP using the OpenSSL library;

<?php
// Encrypt the sensitive data using the OpenSSL library
$ciphertext = openssl_encrypt($_POST['sensitive_data'], 'AES-256-CBC', $key);

// Store the encrypted data in the database
$sql = "INSERT INTO sensitive_data (ciphertext) VALUES ('$ciphertext')";
mysqli_query($conn, $sql);
?>

Enforce Strong Password Policies

To enforce strong password policies the system should check the length and complexity of the password and require a minimum length and combination of characters. (Such as uppercase letters, lowercase letters, numbers and symbols). Here is an illustration of how to enforce strong password policies in PHP;

<?php
$password = $_POST['password'];

// Check the length of the password
if (strlen($password) < 8) {
    echo "Error: Password must be at least 8 characters long.";
    exit;
}

// Check for a combination of characters
if (!preg_match('/[A-Z]/', $password) || !preg_match('/[a-z]/', $password) || !preg_match('/[0-9]/', $password)) {
    echo "Error: Password must contain a combination of uppercase letters, lowercase letters, and numbers.";
    exit;
}

// Hash the password using bcrypt
$password = password_hash($password, PASSWORD_BCRYPT);

// Store the hashed password in the database
$sql = "INSERT INTO users (password) VALUES ('$password')";
mysqli_query($conn, $sql);
?>

Use Two-Factor Authentication (2FA)

Two-factor authentication (2FA) is a security process that requires a user to provide two separate authentication factors to access an account. This can include something the user knows (such as a password) and something the user has (such as a phone with a one-time code). Here is an example of how to implement 2FA in PHP using the Google Authenticator library;

<?php
// Check if the user has enabled 2FA
if ($_SESSION['2fa_enabled'] == true) {
    // Generate a one-time code using the Google Authenticator library
    $code = GoogleAuthenticator::generateCode();

    // Send the one-time code to the user's phone
    // ...

    // Verify the one-time code entered by the user
    if ($_POST['2fa_code'] == $code) {
        // Allow access to the account
        // ...
    } else {
        echo "Error: Invalid 2FA code.";
        exit;
    }
}
?>

By implementing these best practices; You can ensure that your authentication process is secure & protected against potential threats.

Summary

Broken user authentication can leave an application vulnerable to security threats such as unauthorized access, identity theft and data breaches. To prevent BUA it’s crucial to implement secure practices such as using strong passwords, avoiding password reuse, implementing 2FA and encrypting sensitive information. You can assure the security of your application and safeguard the sensitive data of your users by following these procedures.

References

OWASP API Security Project