Broken Function Level Authorization

Hello, API Security Enthusiasts!
Welcome to the exciting world of API security, where we learn about the ins and outs of keeping our API calls safe and secure. In this article, we’ll be focusing on a particularly tricky topic: Broken Function Level Authorization.
Have you ever wondered why some websites ask you to log in even when you’re just trying to look at a cute cat video? That’s because they want to make sure only authorized users have access to sensitive information. Similarly, APIs also need to have proper authorization to prevent unauthorized access to sensitive data.

Broken Function Level Authorization occurs when an API fails to properly control access to specific functions, allowing unauthorized users to access sensitive data. This can lead to major security breaches and harm to both the users and the company.
So let’s gear up and learn how to detect and prevent Broken Function Level Authorization in our APIs.

Real-World Scenario

Let’s say you’re building a blogging platform & you want to allow users to delete their own posts. However, you don’t want unauthorized users to delete other users’ posts. Unfortunately, due to a mistake in your code, the authorization check is not being performed properly. Here’s what that might look like in PHP:

<?php
// Get the post ID from the URL
$post_id = $_GET['post_id'];

// Get the post information from the database
$query = "SELECT * FROM posts WHERE id = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$post_id]);
$post = $stmt->fetch();

// Delete the post from the database
$query = "DELETE FROM posts WHERE id = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$post_id]);

?>

In this illustration, we start by getting the ‘post_id‘ from the URL using the ‘$_GET‘ variable. Then, we use that ‘post_id‘ to retrieve the post information from the ‘posts‘ table in the database.
Finally, we delete the post from the database using the same ‘post_id‘.
The problem with this code is that there is no authorization check to see if the user is allowed to delete this post. As a result, any user can delete any post by simply changing the ‘post_id‘ in the URL. This is an illustration of broken function-level authorization.
We must incorporate an authorization check to determine the user’s ownership of the post before granting them the ability to delete it in order to resolve this problem. Here’s what that might seem in PHP:

<?php

// Check if the user is logged in
if (!isset($_SESSION['user_id'])) {
  header('Location: login.php');
  exit;
}

// Get the post ID and user ID from the URL and session respectively
$post_id = $_GET['post_id'];
$user_id = $_SESSION['user_id'];

// Get the post information from the database
$query = "SELECT * FROM posts WHERE id = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$post_id]);
$post = $stmt->fetch();

// Check if the user is authorized to delete this post
if ($post['user_id'] != $user_id) {
  header('Location: 403.php');
  exit;
}

// Delete the post from the database
$query = "DELETE FROM posts WHERE id = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$post_id]);

?>

In this revised version, we first check if the user is logged in by checking the ‘$_SESSION[‘user_id’]‘ variable. If the user is not logged in, they are redirected to the login-page.
Next, we get the post_id from the URL and the ‘user_id‘ from the session. We then retrieve the post information from the ‘posts‘ table in the database.
After that we compare the ‘user_id‘ of the post with the ‘user_id‘ from the session. If they are not the same, the user is not authorized to delete the post & they are redirected to an error page with an HTTP status code of 403 (Forbidden).

Finally, if the authorization checks pass, the post is deleted from the database as before.
In this revised version, the authorization check is performed properly & unauthorized users are prevented from deleting other users’ posts.

Some Vulnerabilities and their Mitigations

Broken function level authorization can lead to several serious vulnerabilities, including:

SQL Injection

Malicious users may insert harmful SQL code into an application that does not check/escape user input in SQL queries. Leaving the program open to unauthorized access to sensitive data or data alteration.
For instance. The following code enables a user to conduct a product search:

if(isset($_GET['query'])){
$query = $_GET['query'];
$sql = "SELECT * FROM products WHERE name LIKE '%$query%'";
$result = mysqli_query($conn, $sql);
}

To prevent this vulnerability, the code should validate and escape user input:

if(isset($_GET['query'])){
$query = mysqli_real_escape_string($conn, $_GET['query']);
$sql = "SELECT * FROM products WHERE name LIKE '%$query%'";
$result = mysqli_query($conn, $sql);
}

Unrestricted File Uploads

An application that allows users to upload files to the server but fails to check the file type or size can be vulnerable to malicious users uploading executable files to the server and running them.
For instance, the code that follows enables unrestricted file uploading to the server:

if(isset($_FILES['file'])){
   move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/uploads/' . $_FILES['file']['name']);
}

To prevent this vulnerability, the code should check the file type and size before uploading it to the server:

if(isset($_FILES['file'])){
   $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
   $maxSize = 2 * 1024 * 1024; // 2 MB
   if(in_array($_FILES['file']['type'], $allowedTypes) && $_FILES['file']['size'] <= $maxSize){
      move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/uploads/' . $_FILES['file']['name']);
   } else {
      echo "File type not allowed or too large";
   }
}

Insecure Direct Object References (IDOR)

An application that retrieves data from the database using a user-supplied identifier, such as an ID without properly validating or sanitizing the input can lead to unauthorized access to sensitive information.
For illustration, the following code retrieves a user’s profile information using the user ID from the URL:

$userId = $_GET['id'];
$query = "SELECT * FROM users WHERE id = '$userId'";
$result = mysqli_query($conn, $query);
$user = mysqli_fetch_assoc($result);

To prevent this vulnerability, the code should validate and sanitize the input:

$userId = intval($_GET['id']);
$query = "SELECT * FROM users WHERE id = '$userId'";
$result = mysqli_query($conn, $query);
if(mysqli_num_rows($result) > 0){
   $user = mysqli_fetch_assoc($result);
} else {
   echo "User not found";
}

Cross-Site Scripting (XSS)

Malicious users may insert malicious scripts into an application that does not validate or escape user input making the application vulnerable to execution by other users.
As an illustration, the code that follows enables users to post comments:

if(isset($_POST['comment'])){
$comment = $_POST['comment'];
$query = "INSERT INTO comments (comment) VALUES ('$comment')";
mysqli_query($conn, $query);
}

To prevent this vulnerability, the code should validate and escape user input:

if(isset($_POST['comment'])){
$comment = htmlspecialchars($_POST['comment']);
$query = "INSERT INTO comments (comment) VALUES ('$comment')";
mysqli_query($conn, $query);
}

Cross-Site Request Forgery (CSRF)

An application that fails to validate the origin of requests can be vulnerable to malicious users performing actions on behalf of other users such as changing passwords or posting comments.
For illustration, the code that follows enables a user to modify their password via a form.

if(isset($_POST['password'])){
   $password = $_POST['password'];
   $query = "UPDATE users SET password = '$password' WHERE id = '$_SESSION['userId']'";
   mysqli_query($conn, $query);
}

To prevent this vulnerability, the code should validate the origin of the request using a CSRF token:

if(isset($_POST['password']) && isset($_POST['csrf_token']) && $_POST['csrf_token'] === $_SESSION['csrf_token']){
$password = $_POST['password'];
$query = "UPDATE users SET password = '$password' WHERE id = '$_SESSION['userId']'";
mysqli_query($conn, $query);
} else {
echo "Invalid request";
}

These are just a few examples of the many vulnerabilities that can result from broken function level authorization in PHP. It’s important to always validate and sanitize user input, escape data before using it in SQL queries, and implement proper authentication and authorization controls to keep your applications secure.

Summary

Broken function level authorization (BFLA) occurs when an application does not properly enforce access controls, allowing unauthorized users to access or modify sensitive information or resources. This can result in security vulnerabilities such as privilege escalation, cross-site request forgery, cross-site scripting and SQL injection. To prevent broken function level authorization, it is important to validate and sanitize user input, escape data before using it in SQL queries, and implement proper authentication and authorization controls in your application.

References

OWASP API Security Project