Skip to main content

Finding and fixing insecure direct object references in Python

Written by:

Keshav Malik

wordpress-sync/feature-python-linting

July 19, 2023

0 mins read

An insecure direct object reference (IDOR) is a security vulnerability that occurs when a system’s implementation allows attackers to directly access and manipulate sensitive objects or resources without authorization checks. For example, an IDOR can arise when an application provides direct access to objects based on user-supplied input, allowing an attacker to bypass authorization.

Understanding IDOR vulnerabilities

We must tackle IDORs to maintain the confidentiality, integrity, and availability of the sensitive data handled by your Python applications. Developers must understand IDOR vulnerabilities to prevent unauthorized access, maintain user trust, and avoid potentially severe financial and legal consequences. 

Types of IDOR

IDORs take different forms depending on application implementation and functionality.

IDOR with reference to objects

This form of IDOR occurs when an attacker can access or modify an unauthorized object. A common example is when a web application allows access to sensitive data such as bank accounts via a simple request, like example.com/accounts?id={account_id}. If the application doesn’t effectively verify the requester’s permissions, unauthorized users can view or manipulate account information.

IDOR with reference to files

This type of IDOR involves the unauthorized retrieval of files. For example, if a chat application stores confidential conversation logs as incrementing numbered files, an attacker could access private conversations by making similarly numbered requests — example.com/1.log, example.com/2.log, and so on.

Blind IDOR

Blind IDOR refers to cases where the exploitation isn’t directly visible in the server response, even though unauthorized modification may have occurred. With blind IDOR, an attacker can change another user’s private data without viewing it. For example, if a web application allows a user to modify their profile settings via an API call, like example.com/users/update?id={user_id}, an attacker could manipulate the user_id in the request, changing a user’s profile settings without directly viewing their data.

Common patterns to help us spot IDOR vulnerabilities in code

Detecting IDOR vulnerabilities in code requires vigilance and an understanding of common patterns:

  • Lack of access control: An application that takes user input to directly access resources — without verifying user authorization — probably has an IDOR vulnerability.

  • Guessable identifiers: Easily guessable identifiers — such as sequential integers (user_id=99001) or short strings (user_id=user1) — increase the risk of IDOR vulnerabilities in an application. Using hard-to-guess identifiers, such as Universal Unique Identifiers (UUIDs), can mitigate this risk.

  • Direct access to files: An application that gives users direct access to any resources — such as images or CSV files — via URLs or request parameters is vulnerable to IDORs. We must ensure proper control mechanisms are in place to avoid giving users direct access to files.

Creating a Python application with IDOR using FastAPI

In this section, we’ll create a web API vulnerable to IDOR using the Python framework FastAPI. Then, we’ll examine how to remediate the vulnerability.

To follow along with this hands-on tutorial, please ensure you have Python 3.x installed on your machine.

Getting started

First, we need a new application. Create a new directory for your project and move into the directory:

mkdir fastapi-app; cd fastapi-app

Then, install FastAPI, Uvicorn, SQLAlchemy, and SQLite with the following command:

pip install fastapi uvicorn sqlalchemy

Finally, create a file named main.py in the project directory.

Creating a vulnerable API using FastAPI

Now that we’ve set up our directory structure and the requirements, let's write the code containing an IDOR vulnerability. First, copy the code below into main.py:

1from fastapi import FastAPI, HTTPException, Depends
2from sqlalchemy import create_engine, Column, Integer, String
3from sqlalchemy.ext.declarative import declarative_base
4from sqlalchemy.orm import sessionmaker, Session
5
6app = FastAPI()
7
8# SQLite database configuration
9DATABASE_URL = "sqlite:///./test.db"
10engine = create_engine(DATABASE_URL)
11SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12
13Base = declarative_base()
14
15# User model
16class User(Base):
17    __tablename__ = "users"
18
19    id = Column(Integer, primary_key=True, index=True)
20    uuid = Column(String, unique=True, index=True)
21    username = Column(String, unique=True, index=True)
22    email = Column(String, unique=True, index=True)
23
24Base.metadata.create_all(bind=engine)
25
26# Dependency for database session
27def get_db():
28    db = SessionLocal()
29    try:
30        yield db
31    finally:
32        db.close()
33
34@app.get("/users/{user_id}")
35async def get_user_by_id(user_id: int, db: Session = Depends(get_db)):
36    user = db.query(User).filter(User.id == user_id).first()
37    if user is None:
38        raise HTTPException(status_code=404, detail="User not found")
39    return user

The IDOR vulnerability is in the /users/user_id endpoint. As the application has no access controls, any user can view other users’ data by changing the user_id in the URL.

Identifying and fixing IDOR vulnerabilities

Now, let’s find and fix the IDOR security flaw in our FastAPI application.

Finding vulnerabilities

To identify IDOR vulnerabilities, we must look for specific patterns in the code. In the FastAPI application, we should examine the /users/{user_id} endpoint, which represents a potential IDOR vulnerability for the following reasons:

  • Unprotected direct object references: In the get_user_by_id function, the user_id parameter directly refers to the User object in the database. Any client could manipulate the user_id value in the request to access the data of other users because no access control mechanisms protect this direct reference.

  • Lack of authorization: In the same get_user_by_id function, there are no checks to verify whether the requesting user is authorized to access the requested user data. Any user can access any other user’s data by manipulating the user_id parameter.

Fixing vulnerable code

To fix the IDOR vulnerability, we need to update the /users/{user_id} endpoint to have proper access control checks. 

Let’s simulate an authenticated user and only allow access if the user_id in the URL matches the authenticated user’s ID. For the purposes of demonstration, let’s assume that the authenticated user has an ID of 1. Here’s the updated code:

1@app.get("/users/{user_id}")
2async def get_secure_user(user_id: int, db: Session = Depends(get_db)):
3    # Assume user authentication and authorization are performed here
4    authenticated_user_id = "hbecec-edeek-wxwexe-cdcece" # better to use UUIDs
5
6    if authenticated_user_id != user_id:
7        raise HTTPException(status_code=403, detail="Not authorized to access this user")
8
9    user = db.query(User).filter(User.id == user_id).first()
10    if user is None:
11        raise HTTPException(status_code=404, detail="User not found")
12    return user

In this code block, we added an access control check that compares the user_id from the URL to the authenticated_user_id. The application raises an HTTP 403 Forbidden error if the IDs don’t match. This error prevents unauthorized users from accessing other user information, fixing the IDOR vulnerability.

Detecting and fixing security issues with Snyk

Snyk is a security solution with software composition analysis (SCA) and static application security testing (SAST) capabilities. Let’s use Snyk to help us find and fix security issues in our FastAPI application. 

Installing and authenticating Snyk CLI

To get started, install the Snyk CLI on your machine, following the installation guide in the documentation.

After installing the CLI, you need to authenticate your Snyk account. Run the snyk auth command in the terminal, which will open a browser window where you can sign in with your GitHub or Google account.

Scanning dependencies for vulnerabilities

To scan the project for vulnerabilities in the third-party libraries, we need a requirements.txt file. Create requirements.txt with the following command:

pip freeze > requirements.txt

Next, scan your project dependencies for vulnerabilities by executing the following command in the terminal:

snyk test --file=requirements.txt --skip-unresolved

This command analyzes your dependencies, then identifies and describes vulnerabilities, detailing their severity and giving remediation guidance. For instance, you might discover a medium-severity denial of service (DoS) vulnerability in starlette@0.14.2.

Note: Snyk can’t scan the project if a required package is missing. The –skip-unresolved flag overrides this behavior.

Fixing vulnerabilities with Snyk

Snyk can also help us fix vulnerabilities. The snyk fix command can automatically solve some detected issues. Snyk will upgrade or patch the affected dependencies to secure versions, keeping applications up-to-date and secure.

Using Snyk Code for static code analysis

Snyk also has a powerful static application security testing (SAST) solution. The snyk code command can analyze your application code, identify security issues, and provide detailed information on how to fix them.

To use snyk code, run the following command in your terminal:

snyk code test

Snyk Code will perform a deep analysis of your codebase, highlighting any security vulnerabilities present. It also offers remediation advice for these issues, helping you maintain a secure and reliable application.

IDOR vulnerabilities explained

As developers, we must ensure our applications are secure. IDORs threaten web applications, so we must learn how to find and fix them. With a good understanding of IDOR vulnerabilities, you can build Python applications that are both performant and secure. 

In this tutorial, you learned how to find and patch IDOR vulnerabilities in a Python application. We also examined how Snyk can help you identify and rectify security risks in your project dependencies and the application code itself. Snyk is a powerful security tool and integrating it into your development routine provides an extra layer of armor to keep your code sturdy.

Take a closer look at what Snyk has under the hood with a free trial.

wordpress-sync/feature-python-linting

Guide to Choosing a SAST Solution

See the process for assessing, selecting, and implementing a modern SAST solution based on a four phase process and find the best fit for your specific security needs.