The illusion of default security
Many Python developers assume that using Django makes them immune to attacks. They trust the built-in Object-Relational Mapper (ORM). They trust the authentication middleware. They trust the CSRF protection.
This trust is dangerous. Frameworks protect you from the vulnerabilities of 2012 (like basic SQL injection and Cross-Site Scripting). They do not protect you from the complex business logic flaws that hackers exploit today. When you expose your Django application as an API using Django REST Framework, you create an entirely new attack surface. We attack that surface manually.
Building a fintech API in Python? We will break it before the hackers do.
Book a Django PentestAuditing Object-Level Permissions (BOLA/IDOR)
Django REST Framework implements general permission classes like IsAuthenticated to check if a user is logged in. However, it does not enforce object-level validation by default.
If your viewset retrieves records using primary keys, we test if we can modify the request parameter to access another user's profile or transactions. This is known as Broken Object Level Authorization (BOLA). It is the number one vulnerability on the OWASP API Top 10 list.
To prevent this, you must override the get_queryset method or use custom permission classes:
# SECURE: Queryset restricted to the active user session
class TransactionViewSet(viewsets.ModelViewSet):
serializer_class = TransactionSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
# Only return records belonging to the logged-in user
return Transaction.objects.filter(user=self.request.user) During a penetration test, we create two separate accounts: User A and User B. We log in as User A. We capture an API request meant to fetch User A's transaction history (e.g., `/api/transactions/105/`). We change the ID to 106. If the server returns User B's transaction history, you fail the test. We report a critical vulnerability.
An invoice view uses an unfiltered queryset
A view based on RetrieveAPIView and Invoice.objects.all() can return another tenant’s invoice when the caller changes the URL ID. Filter the queryset by the verified tenant and run object permission checks before serialization.
Testing serializer validation and parameter injection
Django serializers validate incoming JSON payloads. But developers sometimes write custom validate_ methods that bypass integrity checks or trust input fields too much. This creates a vulnerability called Mass Assignment.
We check if serializers allow write access to read-only fields. For example, if your user profile serializer permits updates to fields like is_staff or role, an attacker can modify their role status by injecting these parameters into a profile update request.
We send massive JSON payloads to your API endpoints. We guess common administrative fields like `is_admin`, `is_superuser`, `balance`, and `tier`. If your serializer accepts these fields and saves them to the database, we instantly elevate our privileges from a standard user to a system administrator. You must explicitly set `read_only_fields` in your serializer Meta class to prevent this.
Raw SQL queries and ORM Injection
While Django's ORM protects against SQL injection, developers occasionally write raw queries using the extra() method or connection.cursor() to execute complex database operations. They do this to optimize slow queries or write complex joins that the ORM struggles with.
If user input is concatenated directly into these raw query strings, SQL injection occurs. We test all parameters with injection payloads to identify database weaknesses. We use tools like SQLMap, but we also manually craft UNION-based payloads. We attempt to read the Django `auth_user` table. We attempt to extract password hashes. If you concatenate strings instead of using parameterized queries, we will dump your database.
JWT Configuration and Token Forgery
Most Django REST APIs use JSON Web Tokens (JWT) for stateless authentication. We test your JWT implementation aggressively.
We check if your `SECRET_KEY` is weak. We attempt to crack it offline using hashcat. We test if your server accepts tokens signed with the "None" algorithm. We check if you validate the token expiration correctly. We test if a token generated for your staging environment is mistakenly accepted by your production environment.
Rate Limiting and API Abuse
Django REST Framework includes built-in throttling classes like `AnonRateThrottle` and `UserRateThrottle`. Many developers forget to configure them, or they set the limits too high.
We launch brute force attacks against your `/api/login/` endpoints. We attempt to guess OTPs (One Time Passwords) sent to mobile numbers. If your API does not block us after five failed attempts, we will continue until we compromise an account. We also test for race conditions by sending hundreds of concurrent requests to payment and withdrawal endpoints.
Building a fintech API in Python? We will break it before the hackers do.
Book a Django PentestDjango API Pentest Checklist
To secure your Django backend before we audit it, verify these items:
- Check get_queryset configurations: Ensure all detail views filter resources by the authenticated user's ID.
- Read-only serializer fields: Explicitly configure fields like
balance,user_id, androleas read-only. - Harden CORS settings: Avoid using wildcards in your
CORS_ALLOW_ALL_ORIGINSconfiguration. Only allow trusted domains. - Disable Debug Mode: Never set `DEBUG = True` in production. It exposes full stack traces and sensitive environment variables.
- Use Parameterized Queries: If you must write raw SQL, pass variables as a list, never as concatenated strings.
Test Django REST Framework with an access matrix
Create accounts for an anonymous user, a normal user, a second tenant, support staff, and an administrator. Run each endpoint with every account. Cover list, detail, create, update, partial update, delete, export, and custom @action routes.
- Queryset test: change object IDs and filters. Confirm records from another tenant never appear.
- Serializer test: submit read-only fields such as
owner,tenant,status, andis_staff. The API must ignore or reject them. - Permission test: call custom actions directly. Verify both
has_permissionandhas_object_permissionrun where needed. - Parser test: repeat JSON requests as form data and multipart data. Each parser must reach the same validation rules.
- Transaction test: force an error after the first database change. Confirm
transaction.atomic()prevents partial state.
Keep the endpoint matrix, request and response pairs, SQL or audit logs, and the database record used for each check. A clean retest shows a 403 or 404 response and no change to the protected object.
Let Simpa Labs audit your Django stack
We specialize in finding complex logic and authorization issues in Django web applications. We do not use generic vulnerability scanners. We read your code. We test your endpoints manually. We provide exact fixes for every flaw we find.
Frequently asked questions
Does Django's IsAuthenticated permission class protect my data?
No. IsAuthenticated only verifies that the user is logged in. It does not verify that the user owns the data they are trying to access. You must implement object-level permissions to prevent Broken Object Level Authorization (BOLA).
Are Django serializers secure by default?
They are secure against basic injection, but they are vulnerable to Mass Assignment. If you do not explicitly mark sensitive fields like 'is_staff' or 'balance' as read-only, an attacker can submit them in a JSON payload and modify their own database record.
Can Django ORM be hacked with SQL injection?
Yes. While standard ORM methods (filter, exclude) are highly secure, developers often write raw SQL using the extra() method or connection.cursor(). If user input touches those raw queries, your database is compromised.