Practical Guide to Protecting APIs from OWASP Top 10 Vulnerabilities: Python Examples

Tr0jan_Horse

Moderator
Staff member
MODERATOR
ULTIMATE
PREMIUM
MEMBER
Joined
Oct 23, 2024
Messages
304
Reaction score
8,792
Deposit
0$
1748862532797-webp.79685


Your Python API may be leaking customer data right now! It's not a hoax: 7 out of 10 leaks over the past year (as of 2024 and current trends) are APIs' fault. We'll show you how to patch the biggest holes in the OWASP Top 10 and sleep soundly.

APIs (Application Programming Interface) have become the backbone of digital services, but their security is often an afterthought. OWASP regularly updates its list of the most dangerous API vulnerabilities, and in this article we will focus on the most popular and critical ones, providing practical solutions in Python. This article will focus on Flask examples, but the principles and many approaches discussed are also applicable to FastAPI, which also offers powerful built-in security features, such as automatic data validation via Pydantic.

You will learn:
  • What vulnerabilities are included in the OWASP API Security Top 10 (current version 2023).
  • How to detect and fix them in your code.
  • Practical examples of securing Python APIs using Flask (with mention of principles applicable to FastAPI).

[h3]Full list of OWASP API Security Top 10[/h3]The OWASP API Security Top 10 is a universal standard for identifying and preventing common API vulnerabilities. Here is the full list (2023 version):
[ol]
<li>API1:2023 Broken Object Level Authorization (BOLA)</li>
<li>API2:2023 Broken User Authentication</li>
<li>API3:2023 Broken Object Property Level Authorization (Note: your original list had "Excessive Data Exposure", which is closely related, but API3:2023 is more specific. If you were looking at the 2019 list, it had Excessive Data Exposure. Check which version you're using, I'll leave yours up but note it.)</li>
<li>API4:2023 Lack of Resources & Rate Limiting</li>
<li>API5:2023 Broken Function Level Authorization</li>
<li>API6:2023 Mass Assignment (Renamed to Unrestricted Access to Sensitive Business Flows in the new 2023 draft, but Mass Assignment remains a key issue)</li>
<li>API7:2023 Security Misconfiguration</li>
<li>API8:2023 Injection</li>
<li>API9:2023 Improper Assets Management</li>
<li>API10:2023 Insufficient Logging & Monitoring</li>
[/ol]Each of these points poses a serious risk to API security and deserves special attention. In this article, we will focus on several of them that are common in practice and can have critical consequences. The article will focus on BOLA, Injection, CORS (as part of Security Misconfiguration) and Rate Limiting as some of the most critical and frequently encountered threats according to the 2023 list. However, each section will also provide a brief overview of the other items so that you can get the full picture.

[h4]1. API1:2023 Broken Object Level Authorization (BOLA): the most common threat[/h4]

What is it? A user can access other users' data by replacing the object ID in the request.

Example of vulnerable code:

Code:
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_data(user_id):
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

Vulnerability: No check whether it belongs
Code:
user_id
the current authorized user.

Corrected version:

Code:
from flask_jwt_extended import jwt_required, get_jwt_identity

@app.route('/api/user/<int:user_id>', methods=['GET'])
@jwt_required()
def get_user_data(user_id):
    current_user_id = get_jwt_identity()  # Получаем ID текущего пользователя из JWT токена
    if user_id != current_user_id:
        return jsonify({"error": "Unauthorized"}), 403
    user = User.query.get(user_id)
    if not user:
        return jsonify({"error": "User  not found"}), 404
    return jsonify(user.to_dict())

Similar points to note:
  • API2:2023 Broken User Authentication: errors in token validation, weak passwords, no session expiration mechanism or token management (e.g. no revocation of compromised tokens).
  • API5:2023 Broken Function Level Authorization: No check of permissions to perform an action (e.g. a standard user is attempting to perform an administrative action). This differs from BOLA in that BOLA is concerned with access to objects, while BFLA is concerned with access to functions/operations.

[h4]2. API8:2023 Injection attacks: SQLi, NoSQLi and Command Injection[/h4]

What is it? Unfiltered or improperly processed input data allows an attacker to inject malicious code or commands into queries to databases, the operating system, or other interpreters.

SQL injection (vulnerable code):

Code:
# КАТЕГОРИЧЕСКИ НЕПРАВИЛЬНО!
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
# cursor.execute(query)

Safe option (parameterized query or ORM):

Code:
# Используем ORM (SQLAlchemy) - предпочтительно
user = User.query.filter_by(username=username).first()
# Или параметризованный запрос (пример для psycopg2)
# cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
# user_data = cursor.fetchone()

NoSQL injection (example vulnerability):

Let’s assume the following code is used to find a user, and the variable
Code:
username_input
comes from an unverified source:

Code:
# Уязвимо, если 'username_input' может содержать операторы MongoDB, например: {"$ne": "admin"}
# username_input = get_user_input() # مثلاً, {"$gt": ""}
# results = db.users.find({"username": username_input})

Fix (focus on secure authentication and injection prevention):

While string key lookups themselves are often safe from classic NoSQL injections, it is important to take a holistic approach to security:

Code:
# from bson import ObjectId # Может понадобиться для работы с ID
from werkzeug.security import check_password_hash # Для безопасной проверки пароля
# 1. Валидация и санитизация ввода:
# Убедимся, что username - это строка и не содержит опасных символов/структур.
# Для FastAPI это часто делается через Pydantic модели.
# Для Flask можно использовать marshmallow или специализированные библиотеки валидации.
# username_from_request = "example"  # Это должно быть получено из запроса и провалидировано
# if not isinstance(username_from_request, str) or not username_from_request.isalnum(): # Упрощенный пример
#     return jsonify({"error": "Invalid username format"}), 400
# user = db.users.find_one({"username": username_from_request})
# 2. Безопасная проверка пароля (относится к API2:2023 Broken User Authentication)
# if user and check_password_hash(user.get("password_hash"), password_from_request): # Пароль в БД должен быть хеширован
#     # Авторизация успешна
#     pass
# else:
#     # Ошибка аутентификации
#     pass

Key points for protecting against NoSQL injections:
  • Never construct queries by concatenating strings with user input if this might affect the query structure or operators.
  • Always strictly validate and sanitize input data. Ensure that the data matches the expected type and format (e.g.
    Code:
    username
    must be a string, not an object). Use the "default deny" principle.
  • Use ODMs (Object-Document Mappers) like MongoEngine or Beanie, which often provide built-in injection protection mechanisms.
  • If your DBMS driver supports parameterized queries or similar secure APIs, use them.
  • Avoid using functions that execute code inside the DBMS based on user input (e.g. operator
    Code:
    $where
    with JavaScript in MongoDB).

Related items:
  • API6:2023 Mass Assignment (or Unrestricted Access to Sensitive Business Flows): Automatically associating user data with an object (e.g. via JSON) without filtering the fields may allow an attacker to modify fields of the object that they should not have control over (e.g.
    Code:
    is_admin = True
    ). Always use DTOs (Data Transfer Objects) or models with explicitly defined fields.

[h4]3. API7:2023 Security Misconfiguration (example: CORS misconfiguration)[/h4]

What is it? Security configuration errors at any level of the application stack, including cloud services, network devices, HTTP headers, and using default settings without adapting them to security requirements.

An example of such an error is too broad a CORS (Cross-Origin Resource Sharing) setting.

Vulnerable CORS configuration:

Code:
from flask_cors import CORS
CORS(app)  # Разрешает запросы с ЛЮБЫХ доменов! Это опасно для большинства API.

Secure CORS setup (example):

Code:
from flask_cors import CORS
CORS(app, resources={
    r"/api/v1/*": {
        "origins": ["https://yourdomain.com", "https://trusted-partner.com"],  # Список доверенных источников
        "methods": ["GET", "POST", "PUT"],  # Разрешенные методы
        "allow_headers": ["Content-Type", "Authorization"],  # Разрешенные заголовки
        "expose_headers": ["X-Custom-Header"],  # Заголовки, доступные клиенту
        "supports_credentials": True  # Если нужны куки или заголовки авторизации
    }
})

Other aspects of Security Misconfiguration:
  • Lack of up-to-date security patches.
  • Debug mode enabled in production.
  • Using default credentials.
  • Overly verbose error messages that reveal internal structure.
  • Incorrectly configured HTTP security headers (X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, etc.).

[h4]4. API4:2023 Lack of Resources & Rate Limiting[/h4]

What is it? No restrictions on the number or frequency of requests from the client, as well as on the resources consumed (memory, CPU, number of processes), which makes the API vulnerable to DoS attacks, brute force and other types of abuse.

Solution (example with Flask-Limiter):

Code:
from flask import Flask, jsonify, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    app,
    key_func=get_remote_address,  # Ограничение по IP-адресу клиента
    default_limits=["200 per day", "50 per hour"]  # Общие лимиты по умолчанию
)

@app.route('/api/login', methods=['POST'])
@limiter.limit("5 per minute")  # Не более 5 попыток входа в минуту для этого эндпоинта
def login():
    # Логика авторизации
    # ...
    return jsonify({"message": "Login attempt processed"})

@app.route('/api/resource')
@limiter.limit("100 per hour", override_defaults=True)  # Переопределение лимита для этого ресурса
def get_resource():
    # ...
    return jsonify({"data": "some resource"})

OWASP Top 10 Link:

This directly relates to API4:2023 Lack of Resources & Rate Limiting. Rate limiting is a key method to prevent resource exhaustion, protect against DoS attacks, and API abuse such as brute-force attacks. In addition to rate limiting, it is also important to control the size of requests, the depth of nesting in GraphQL, the number of elements requested, etc.

[h3]Conclusion[/h3]API security is not a one-time setting, but an ongoing process that requires attention at all stages of the application lifecycle. Key recommendations:
[ol]
<li>Always check object-level (BOLA) and function-level (BFLA) authorizations. Ensure that users only have access to the data and operations they have permission to perform.</li>
<li>Carefully filter and validate all input data to ensure it matches the expected type, format, length, and range of values. This is the first line of defense against many attacks, including injections.</li>
<li>Set up strict and proper CORS rules, allowing access only to trusted origins.</li>
<li>Implement Rate Limiting to protect against brute force and DoS attacks and control resource consumption.</li>
<li>Ensure proper authentication management: use strong mechanisms, secure storage of credentials, multi-factor authentication where possible.</li>
<li>Minimize your attack surface by disabling unused features and endpoints.</li>
<li>Update all system components regularly and conduct security audits.</li>
<li>Implement comprehensive logging and monitoring of security events to detect and respond to incidents in a timely manner.</li>
[/ol]

[h3]FAQ[/h3]
[h4]Q: How to check your API for vulnerabilities?[/h4]
A: Use a combination of methods:
  • Static code analysis (SAST).
  • Dynamic Analysis (DAST) using tools such as OWASP ZAP, Burp Suite Pro, Postman (with its testing capabilities).
  • Manual penetration testing (pentesting), especially for complex business logic.
  • Regular code reviews with a focus on security.

[h4]Q: How to avoid excessive logging of data in API?[/h4]
A: Log only the minimum amount of information necessary for auditing and debugging. This could include request statuses, user IDs (without revealing or masking Personally Identifiable Information - PII), types of operations performed, IP addresses (subject to privacy laws). Avoid logging sensitive data such as passwords, full access tokens, financial information in clear text. Use application-level filters to control the content of logs and set up monitoring of security events (e.g. failed login attempts, authorization errors, injection attempts).

[h4]Q: Which Python libraries are the safest for working with APIs?[/h4]
A: FastAPI (with its powerful data validation system via Pydantic) and Flask combined with proven extensions (e.g. Flask-JWT-Extended for tokens, Flask-Limiter for request limiting, SQLAlchemy or other ORMs for secure DB support) are good choices.

It is important to remember that security depends not so much on the library itself, but on its correct configuration, following secure development practices (for example, the principle of least privilege, protection from all OWASP Top 10) and regularly updating dependencies.

[h4]Q: Is there a checklist to ensure API security?[/h4]
A: Yes, you can use checklists based on the OWASP API Security Top 10, OWASP ASVS (Application Security Verification Standard) or NIST SP 800-series. Key points for self-checking:
  • Ensure that all input data is strictly validated.
  • Configure strong authentication and authorization (BOLA, BFLA, Broken Authentication) on each endpoint.
  • Minimize data exposure (Excessive Data Exposure / Broken Object Property Level Authorization).
  • Implement Rate Limiting and resource exhaustion protection.
  • Check your security configuration (CORS, HTTP headers, no debug interfaces in production).
  • Protect yourself from all types of injections.
  • Provide lifecycle management for the API and its resources (Improper Assets Management).
  • Set up Insufficient Logging & Monitoring.
  • Test and review your security configuration regularly.

Did this article help you understand API protection? Great! If you now want to learn not only how to defend yourself, but also how to find vulnerabilities yourself, like the pros do, it's time to master pentesting. By looking at your APIs through the eyes of an attacker, you will learn how to build a truly strong defense. Such practical skills can be obtained, for example, at pentester.codeby.school, where the emphasis is on real techniques and current threats.

[span]What methods and tools for API protection do you use in your projects? Share your experience in the comments! 💬👇[/span]
 
Top Bottom