Broken Object Level Authorization

API security is a critical aspect of any software development project, and broken object level authorization is a crucial component of that security. But what happens when this component is broken? Suddenly, your API is vulnerable to all sorts of attacks and your users’ sensitive information is at risk.

In this blog post, we’ll explore what broken object level authorization is, why it’s a problem, and how to fix it. We’ll use real-life examples and humorous code snippets to make the topic accessible and lighthearted. So whether you’re a seasoned developer or just starting out, this post is for you.

So, put down that hacking tool and grab a cup of tea/coffee. It’s time to learn all about broken object level authorization in API security.

Purpose of the Blog Post

The purpose of this blog post is to provide a comprehensive overview of broken object-level authorization in API security. We will cover what broken object-level authorization is, why it’s a problem and how to fix it. The objective is to provide developers the knowledge and tools required to ensure that the security of their APIs is maintained while also assisting users in understanding the significance of object-level authorization.

Scenario

Suppose you have an API that manages user data for a social media app. You want to ensure that each user can only access their own data and not the data of other users.
Here’s a code example in PHP that implements Object Level Authorization in the API:

<?php

// Get the user ID from the request
$user_id = $_GET['user_id'];

// Get the current user's ID from the database
$current_user_id = get_current_user_id();

// Check if the user ID in the request matches the current user's ID
if ($user_id != $current_user_id) {
  // Return an error if the IDs do not match
  header('HTTP/1.1 401 Unauthorized');
  echo 'You are not authorized to access this data.';
  exit;
}

// Continue with the rest of the API request
// ...

?>

This code snippet checks the ‘user_id‘ from the request and compares it with the ‘current_user_id‘ obtained from the database. If the two IDs do not match, the request is considered unauthorized and an error is returned.
This implementation of Object Level Authorization ensures that each user can only access their own data and not the data of other users, helping to protect the privacy and security of user data in the social media app.

Some Examples

Lack of Authentication

// Example in Node.js
app.get('/api/user/:id', (req, res) => {
  const user = db.find(u => u.id === req.params.id);
  res.json(user);
});

To determine whether the user is permitted access to the requested resource in this example, there is no authentication method in place. It follows that information about every user in the database can be accessed by any user who knows the URL. The code might be updated to check the user’s credentials before returning the user data to address this:

// Example in Node.js
app.get('/api/user/:id', (req, res) => {
  if (!req.user) {
    res.status(401).json({ error: 'Unauthorized' });
    return;
  }

  const user = db.find(u => u.id === req.params.id);
  if (user.id !== req.

Weak Permission Checking

// Example in PHP
public function editPost(int $postId, int $userId, string $title, string $content) {
  $post = Post::find($postId);
  $post->title = $title;
  $post->content = $content;
  $post->save();

  return response()->json(['message' => 'Post updated successfully']);
}

In this case, there is no safeguard to guarantee that the post can only be edited by the author. A user could potentially modify another user’s post by simply passing in a different ‘$userId‘ in the request. To fix this, the code should be updated to check that the ‘$userId‘ matches the post’s author, prior to saving the modifications:

// Example in PHP
public function editPost(int $postId, int $userId, string $title, string $content) {
  $post = Post::find($postId);
  if ($post->author_id !== $userId) {
    return response()->json(['error' => 'You are not authorized to edit this post']);
  }

  $post->title = $title;
  $post->content = $content;
  $post->save();

  return response()->json(['message' => 'Post updated successfully']);
}

Unsecured API Endpoints

// Example in Python
@app.route('/api/admin/<int:id>', methods=['DELETE'])
def delete_admin(id):
    admin = Admin.query.get(id)
    db.session.delete(admin)
    db.session.commit()
    return jsonify({'message': 'Admin deleted successfully'})

For instance, the endpoint for deleting an admin is publicly accessible, which means that anyone could potentially delete an admin account. To fix this, the code could be updated to check that the user making the request is authorized to perform the action:

// Example in Python
@app.route('/api/admin/<int:id>', methods=['DELETE'])
def delete_admin(id):
    if not current_user.is_admin:
        return jsonify({'error': 'You are not authorized to perform this action'})

    admin = Admin.query.get(id)
    db.session.delete(admin)
    db.session.commit()
    return jsonify({'message': 'Admin deleted successfully'})

Real-Life Examples

Broken Object Level Authorization in an E-commerce API

Suppose you’re building an API for an online shopping platform. You allow users to retrieve information about their orders, including details such as the items they’ve purchased, the total cost and the delivery address. However, due to a broken object-level authorization users can access the orders of other users simply by changing the order ID in the API endpoint URL.

Here’s an example in PHP:

// API endpoint for retrieving order details
$app->get('/orders/{orderId}', function ($orderId) use ($app) {
  // Retrieve order details from the database
  $order = getOrderDetails($orderId);

  // Check if the user is authorized to view this order
  if (!isUserAuthorized($order['userId'])) {
    // Return an error if the user is not authorized
    return json_encode(array('error' => 'Unauthorized access'));
  }

  // Return the order details
  return json_encode($order);
});

// Function to check if the user is authorized to view the order
function isUserAuthorized($userId) {
  // In this example, the broken object level authorization allows any user to access any order, so the function always returns true
  return true;
}

Broken Object Level Authorization in a Social Media API

Imagine a social media API that allows users to post and view content. An attacker might be able to access and view another user’s private postings without that user’s consent if object-level authorization is compromised. This could result in a breach of privacy and trust in the social media platform.

Here is an example, of how the code may appear if object level authorization is improperly used:

public function getPost($postId) {
    // Query the database to get the post
    $post = $this->db->query("SELECT * FROM posts WHERE post_id = '$postId'");

    return $post;
}

There is no object level authorization in place to ensure that the user who is trying to access the post is authorized.

Broken Object Level Authorization in a Banking API

Think of a banking API that enables users to transfer money across accounts. If the object level authorization is broken, an attacker would be able to access and transfer money from another user’s account without their permission. This could result in financial loss for the victim and a loss of trust in the bank.
Here is an example: how the code may appear if object level authorization is improperly used:

public function transferMoney($fromAccount, $toAccount, $amount) {
    // Query the database to get the account information
    $fromAccountInfo = $this->db->query("SELECT * FROM accounts WHERE account_number = '$fromAccount'");
    $toAccountInfo = $this->db->query("SELECT * FROM accounts WHERE account_number = '$toAccount'");

    // Deduct the amount from the from account
    $newFromAccountBalance = $fromAccountInfo['balance'] - $amount;
    $this->db->query("UPDATE accounts SET balance = '$newFromAccountBalance' WHERE account_number = '$fromAccount'");

    // Add the amount to the to account
    $newToAccountBalance = $toAccountInfo['balance'] + $amount;
    $this->db->query("UPDATE accounts SET balance = '$newToAccountBalance' WHERE account_number = '$toAccount'");

    return true;
}

For example, there is no object level authorization in place to ensure that the user attempting to transfer money is authorized to do so. Any account might possibly be used without limitation by an attacker to transfer money.

Broken Object Level Authorization in a Healthcare API

Imagine a healthcare API that allows patients to access their medical records. If the object level authorization is broken, an attacker might be able to access and view another patient’s medical records without permission. This could result in a breach of sensitive and private information’s.

public function getMedicalRecord($patientId) {
    // Query the database to get the medical record
    $medicalRecord = $this->db->query("SELECT * FROM medical_records WHERE patient_id = '$patientId'");

    return $medicalRecord;
}

In this case, no object level authorization is in place to make sure the user attempting to access the medical information is authorized. Any medical record could possibly be open to unauthorized access.

Broken Object Level Authorization in a Video Streaming API

Suppose you’re building an API for a video streaming service. You allow users to retrieve information about the videos they’ve rented, including details such as the title, release date and rental period. However, due to a broken object level authorization, users can access the rented videos of other users simply by changing the video ID in the API endpoint URL.

Here’s an example in PHP:

// API endpoint for retrieving rented video details
$app->get('/videos/{videoId}', function ($videoId) use ($app) {
  // Retrieve video details from the database
  $video = getVideoDetails($videoId);

  // Check if the user is authorized to view this video
  if (!isUserAuthorized($video['userId'])) {
    // Return an error if the user is not authorized
    return json_encode(array('error' => 'Unauthorized access'));
  }

  // Return the video details
  return json_encode($video);
});

// Function to check if the user is authorized to view the video
function isUserAuthorized($userId) {
  // In this example, the broken object level authorization allows any user to access any rented video, so the function always returns true
  return true;
}

Why is Broken Object Level Authorization a Problem?

Broken object-level authorization is a problem because it can lead to serious security vulnerabilities that can compromise user data and the integrity of the API.

Security Vulnerabilities

Unauthorized Access

One of the most common security vulnerabilities resulting from broken object level authorization is unauthorized access. When an attacker has access to data or can carry out tasks they shouldn’t be able to perform, this happens. Consider about the case where a user’s profile information is not properly safeguarded for example. An attacker could potentially access this information by modifying the URL of the request.
Here is some sample code that illustrates this vulnerability:

// Example in Node.js
app.get('/users/:id', (req, res) => {
  User.findById(req.params.id, (err, user) => {
    if (err) return res.status(500).send(err);
    res.send(user);
  });
});

In this code, the ‘/users/:id‘ endpoint retrieves user information based on the ‘id‘ provided in the URL. An attacker could potentially access another user’s information by changing the ‘id‘ in the URL. To prevent this, proper object level authorization checks should be in place to ensure that only the intended user’s information is being accessed.

Data Modification

Data modification is another vulnerability of broken object level authorization. When an attacker is capable of altering data that they shouldn’t be able to this happens. Think of a situation where a user’s account information may be modified without required authentication, for instance. An attacker could potentially alter the information of another user to steal their identity or do other harm.

Here is an example of code that demonstrates this vulnerability:

// Example in Ruby on Rails
class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      render json: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  private
    def user_params
      params.require(:user).permit(:email, :password, :name)
    end
end

In this code, the ”update” method is used to modify user information. An attacker could potentially modify another user’s information by changing the ‘id‘ in the URL. To prevent this, proper authentication checks and object level authorization checks should be in place to ensure that only the intended user’s information is being changed.

Risk to User Data

One of the biggest risks of broken object level authorization is the exposure of sensitive user data. This can include personal information, financial information and other sensitive data that should not be accessible to unauthorized users.
Think about the case where a user’s profile data is not properly protected, for instance. The URL of the request could potentially be changed by an attacker to gain access to this data.
An example of PHP code that illustrates this vulnerability is given below:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

In this code, the user information is retrieved based on the ‘id‘ provided in the URL. An attacker could potentially access another user’s information by changing the ‘id‘ in the URL. To prevent this, proper object level authorization checks should be in place to ensure that only the intended user’s information is being accessed.

How to Fix Broken Object Level Authorization

Implementing Object Level Authorization

Implementing object level authorization is the process of adding authorization checks to an API to ensure that only authorized users can access specific resources. This is accomplished by implementing an authorization mechanism, such as Access Control Lists (ACLs) or Role-based Access Control (RBAC). Here is an example of code in PHP that implements object level authorization using RBAC:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

if ($user['id'] !== $_SESSION['user_id'] && $_SESSION['role'] !== 'admin') {
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

In this code, the user information is retrieved based on the ‘id‘ provided in the URL. Before the information is returned, a check is performed to ensure that the user requesting the information is either the intended user or an administrator. If the check fails, a 401 Unauthorized error is returned. In addition to RBAC, object level authorization can also be implemented using ACLs. An ACL is a list of permissions that define who has access to what resources. For example, in a file system, an ACL might specify which users are allowed to read, write, and execute a specific file.
Here is an example of code in PHP that implements object level authorization using ACLs:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

$acl = getAclForUser($user_id);

if (!$acl['read']) {
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

function getAclForUser($user_id) {
  // database query to retrieve ACL information for user
  return $acl;
}

In this code, the user information is retrieved, and the corresponding ACL is retrieved using the ‘getAclForUser‘ function. The ‘read‘ permission is checked and if the user is not allowed to read the resource a 401 Unauthorized error is returned. By implementing object level authorization, developers can ensure that only authorized users have access to sensitive data and resources and that user data is kept secure.

Best Practices for Implementing Object Level Authorization

To ensure that object level authorization is implemented correctly & effectively, there are several best practices that developers should follow. Some of these best practices include;

Use a robust authorization library: Use a robust authorization library that has through extensive testing and has good support. For establishing object level authorization, a popular option is the PHP security library “sentinel“. Here is an instance of PHP code that makes use of the sentinel library:

<?php
use Cartalyst\Sentinel\Laravel\Facades\Sentinel;

$user_id = $_GET['id'];
$user = getUserById($user_id);

if (!Sentinel::hasAccess('user.view')) {
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

In this code, the Sentinel library is used to check if the current user has the user.view permission. If the user does not have this permission, a 401 Unauthorized error is returned.

Log authorization events: By logging authorization events, you can identify unauthorized attempts to access sensitive data and take appropriate action to prevent further breaches. In PHP, you can implement logging of authorization events using various logging frameworks such as Monolog, Log4PHP and others. Example; how to use Monolog to log authorization events in your PHP application:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create a new logger instance
$logger = new Logger('authorization');

// Set the log file location
$logFile = '/path/to/log/file.log';

// Create a new stream handler
$handler = new StreamHandler($logFile, Logger::INFO);

// Add the stream handler to the logger
$logger->pushHandler($handler);

// Log the authorization event
$logger->info('User with ID ' . $userId . ' accessed resource ' . $resourceId); 

In this example; we are using the Monolog logging framework to log the authorization event. First we create a new logger instance and set the name of the logger to “authorization“. We then specify the location of the log file and create a new stream handler for the log file. We set the log level to INFO, which means that only events with a severity level of INFO or higher will be logged.

Define clear authorization rules: It’s crucial to lay down concise, understandable and easy to follow authorization rules. A rule such as “users can only view their own information” should be established for instance; if a user should only be able to read their own information.

Common Mistakes to Avoid

Some of the most common mistakes to avoid include:

Hardcoding authorization rules: Hardcoding authorization rules is a security risk because it makes it easier for attackers to bypass the authorization mechanism. For example; the following code hardcodes the authorization rule that only allows users with the ID of 1 to access certain data:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

if ($user_id != 1) {
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

In this code; if a user with an ID other than 1 tries to access the data they will receive ‘401 Unauthorized error’. This is a security risk because it makes it easy for an attacker to find and exploit the vulnerability.

Not validating user input: Not validating user input is a common mistake that can lead to security vulnerabilities. As an example; the following code does not validate the user ID which makes it possible for an attacker to submit an ID that is not a number:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

if (!Sentinel::hasAccess('user.view')) {
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

In this code; if an attacker submits a string instead of a number for the user ID, the code will break because ‘getUserById‘ is expecting a number. To prevent this vulnerability, it’s important to validate user input and make sure it’s of the correct type.

Not using HTTPS: Not using HTTPS is a common mistake that can lead to security vulnerabilities. For example; the following code does not use HTTPS which makes it possible for an attacker to intercept and modify the data being transmitted:

<?php
$user_id = $_GET['id'];
$user = getUserById($user_id);

if (!Sentinel::hasAccess('user.view')) {	
  header("HTTP/1.1 401 Unauthorized");
  exit();
}

echo json_encode($user);

function getUserById($id) {
  // database query to retrieve user information
  return $user;
}

To prevent Not using HTTPS, you should ensure that all communications between the server and client are secured using HTTPS. You can enforce HTTPS by redirecting all HTTP traffic to HTTPS traffic. Here’s an example code snippet in PHP that redirects all HTTP traffic to HTTPS traffic:

if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
    header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
    exit();
}

This code checks if the HTTPS protocol is used in the request and if not; it redirects the user to the same URL with HTTPS. You should add this code at the beginning of each PHP script to ensure that all requests are made over HTTPS. By enforcing HTTPS on your website or application, you can ensure that all communications between the server and client are secure, preventing security vulnerabilities and protecting sensitive user data.

Summary

In this blog post, we explored the concept of Object Level Authorization in API security and its importance in protecting user data and preventing security vulnerabilities. We also discussed what constitutes broken Object Level Authorization and the consequences it can lead to. We then delved into best practices for implementing Object Level Authorization, common mistakes to avoid, and tools for testing its implementation.

References

OWASP API Security Project