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
:
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
app = FastAPI()
# SQLite database configuration
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# User model
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
uuid = Column(String, unique=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
Base.metadata.create_all(bind=engine)
# Dependency for database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
async def get_user_by_id(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
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, theuser_id
parameter directly refers to theUser
object in the database. Any client could manipulate theuser_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 theuser_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:
@app.get("/users/{user_id}")
async def get_secure_user(user_id: int, db: Session = Depends(get_db)):
# Assume user authentication and authorization are performed here
authenticated_user_id = "hbecec-edeek-wxwexe-cdcece" # better to use UUIDs
if authenticated_user_id != user_id:
raise HTTPException(status_code=403, detail="Not authorized to access this user")
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
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.