Insecure Direct Object Reference Prevention Cheat Sheet / 不安全直接对象引用预防速查表¶
Introduction / 介绍¶
Insecure Direct Object Reference (IDOR) is a vulnerability that arises when attackers can access or modify objects by manipulating identifiers used in a web application's URLs or parameters. It occurs due to missing access control checks, which fail to verify whether a user should be allowed to access specific data.
不安全直接对象引用(IDOR)是一种漏洞,当攻击者可以通过操纵用于Web应用程序URL或参数的标识符来访问或修改对象时出现。这是由于缺少访问控制检查,未能验证用户是否有权访问特定数据。
There are three ingredients to an IDOR:
IDOR有三个要素:
- An object such as an account, document, support ticket, transaction or partner profile.
中文:一个对象,例如账户、文档、支持工单、交易或合作伙伴资料。 - A reference to that object in the form of an ID, UUID, account number, token or slug.
中文:对该对象的引用,形式为ID、UUID、账户号码、令牌或缩略名。 - A missing object-level authorization check that allows a user to access or manipulate an object they should not be able to access.
中文:一个缺失的对象级授权检查,允许用户访问或操作他们不应能够访问的对象。
Examples / 示例¶
For instance, when a user accesses their profile, the application might generate a URL like this:
例如,当用户访问其个人资料时,应用程序可能会生成如下URL:
https://example.org/users/123
The 123 in the URL is a direct reference to the user's record in the database, often represented by the primary key. If an attacker changes this number to 124 and gains access to another user's information, the application is vulnerable to Insecure Direct Object Reference. This happens because the app didn't properly check if the user had permission to view data for user 124 before displaying it.
URL中的123是对数据库中用户记录的直接引用,通常由主键表示。如果攻击者将此数字更改为124并获取另一个用户的个人信息,则应用程序容易受到不安全直接对象引用的攻击。这是因为应用程序在显示之前没有正确检查用户是否有权查看用户124的数据。
In some cases, the identifier may not be in the URL, but rather in the POST body, as shown in the following example:
在某些情况下,标识符可能不在URL中,而是在POST体中,如下例所示:
<form action="/update_profile" method="post">
<!-- Other fields for updating name, email, etc. -->
<input type="hidden" name="user_id" value="12345">
<button type="submit">Update Profile</button>
</form>
In this example, the application allows users to update their profiles by submitting a form with the user ID in a hidden field. If the app doesn't perform proper access control on the server-side, attackers can manipulate the "user_id" field to modify profiles of other users without authorization.
在这个例子中,应用程序允许用户通过提交包含用户ID的隐藏字段的表单来更新他们的个人资料。如果应用程序在服务器端没有执行适当的访问控制,攻击者可以操纵“user_id”字段来修改未经授权的其他用户的个人资料。
IDORs however are not limited to user profiles and sequential IDs. For instance:
IDORs然而并不局限于用户资料和顺序ID。例如:
GET /documents/annual-report.pdf
In this example, the filename acts as the object reference. If an attacker modifies the filename to another valid document, such as:
在这个例子中,文件名充当对象引用。如果攻击者将文件名修改为另一个有效文档,例如:
GET /documents/financial-statement.pdf
and gains access to a document belonging to another user, the application is vulnerable to IDOR. Object references are not limited to numeric identifiers and may include filenames, account numbers, tokens, or other values.
并获取属于另一个用户的文档的访问权限,则应用程序容易受到IDOR攻击。对象引用不仅限于数字标识符,还可能包括文件名、账户号码、令牌或其他值。
Identifier complexity / 标识符复杂性¶
In some cases, using more complex identifiers like GUIDs can make it practically impossible for attackers to guess valid values. However, even with complex identifiers, access control checks are essential. If attackers obtain URLs for unauthorized objects, the application should still block their access attempts.
在某些情况下,使用更复杂的标识符,如GUID,可以使攻击者难以猜测有效值。然而,即使具有复杂的标识符,访问控制检查仍然是必要的。如果攻击者获取了未经授权对象的URL,则应用程序仍然应该阻止他们的访问尝试。
Verifying access controls / 验证访问控制¶
When testing for IDOR vulnerabilities, it is useful to create multiple user accounts with different authorization scopes. This allows developers and testers to verify that object-level authorization checks are consistently enforced.
在测试IDOR漏洞时,创建具有不同授权范围的多个用户账户非常有用。这允许开发人员和测试人员验证对象级授权检查是否始终得到执行。
For example:
例如:
- Create User A and User B.
中文:创建用户A和用户B。 - Create objects owned by each user (documents, support tickets, invoices, etc.).
中文:创建每个用户拥有的对象(文档、支持工单、发票等)。 - Authenticate as User A and attempt to access User B's objects by modifying object references in requests.
中文:以用户A的身份进行认证,并尝试通过修改请求中的对象引用来访问用户B的对象。 - Verify that the application denies unauthorized access regardless of whether the object identifier is predictable or unguessable.
中文:验证应用程序在对象标识符可预测或不可预测的情况下都拒绝未经授权的访问。
This verification should be performed for all operations involving object references, including read, create, update, delete, export, and administrative actions.
应针对涉及对象引用的所有操作执行此验证,包括读取、创建、更新、删除、导出和管理操作。
Mitigation / 缓解措施¶
To mitigate IDOR, implement access control checks for each object that users try to access. Web frameworks often provide ways to facilitate this. Additionally, use complex identifiers as a defense-in-depth measure, but remember that access control is crucial even with these identifiers.
为了缓解IDOR,为用户尝试访问的每个对象实现访问控制检查。Web框架通常提供方便实现这一点的途径。此外,使用复杂标识符作为多层次防御措施,但请记住,即使在这些标识符的情况下,访问控制也非常关键。
Avoid exposing identifiers in URLs and POST bodies if possible. Instead, determine the currently authenticated user from session information. When using multi-step flows, pass identifiers in the session to prevent tampering.
尽可能避免在URL和POST体中暴露标识符。相反,从会话信息中确定当前认证用户。在使用多步骤流程时,通过会话传递标识符以防止篡改。
When looking up objects based on primary keys, use datasets that users have access to. For example, in Ruby on Rails:
在根据主键查找对象时,使用用户可访问的数据集。例如,在Ruby on Rails中:
# vulnerable, searches all projects
@project = Project.find(params[:id])
# secure, searches projects related to the current user
@project = @current_user.projects.find(params[:id])
Verify the user's permission every time an access attempt is made. Implement this structurally using the recommended approach for your web framework.
每次访问尝试时都验证用户的权限。使用您Web框架推荐的方案在结构上实现这一点。
As an additional defense-in-depth measure, replace enumerable numeric identifiers with more complex, random identifiers. You can achieve this by adding a column with random strings in the database table and using those strings in the URLs instead of numeric primary keys. Another option is to use UUIDs or other long random values as primary keys. Avoid encrypting identifiers as it can be challenging to do so securely.
作为深度防御的额外措施,将可枚举的数字标识符替换为更复杂、随机的标识符。您可以通过在数据库表添加包含随机字符串的列,并在URL中使用这些字符串而不是数字主键来实现这一点。另一种选择是使用UUID或其他长随机值作为主键。避免加密标识符,因为这可能难以安全地执行。
Java and Spring Boot example / Java和Spring Boot示例¶
These illustrative examples assume authenticated requests and a policy that only owners can read documents. currentUser() represents an application helper that obtains the user from the trusted authentication context. Consider a Spring Boot endpoint that returns a document by its identifier:
这些示例假设是经过认证的请求和只有所有者才能读取文档的策略。currentUser()代表一个应用程序助手,它从可信的认证上下文中获取用户。考虑一个Spring Boot端点,该端点通过标识符返回文档:
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
return documentRepository.findById(id).orElseThrow();
}
The {id} path variable is controlled by the requester and flows directly into findById(). Nothing verifies that the returned document belongs to the currently authenticated user, so any user who can guess or obtain a valid identifier can read another user's document.
{id}路径变量由请求者控制,直接流入findById()。没有任何东西可以验证返回的文档是否属于当前认证用户,因此任何可以猜测或获取有效标识符的用户都可以读取另一个用户的文档。
Fixing the code / 修复代码¶
Prefer a lookup limited to documents owned by the current user. This mirrors the scoped-query approach shown for Ruby on Rails above:
优先选择仅限于当前用户拥有的文档的查找。这反映了上面为 Ruby on Rails 展示的 scoped-query 方法:
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
return documentRepository.findByIdAndOwnerId(id, currentUser().getId())
.orElseThrow();
}
Alternatively, verify ownership explicitly after fetching the object:
或者,在获取对象后显式验证所有权:
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
Document document = documentRepository.findById(id).orElseThrow();
if (!document.getOwnerId().equals(currentUser().getId())) {
throw new AccessDeniedException("Not allowed");
}
return document;
}
The post-fetch check can distinguish a missing object from one the caller cannot access. If the resource's existence is sensitive, use the scoped lookup and map both cases to the same public response, such as a 404 (Not Found) response. For example, GET /user/john@example.com should not reveal whether an account exists to a caller who is not allowed to know.
获取后的检查可以区分缺失的对象和调用者无法访问的对象。如果资源的存在是敏感的,请使用 scoped 查找并将两种情况映射到相同的公共响应,例如 [404(未找到)响应](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.4)。例如,GET /user/john@example.com 不应向不允许知道账户存在的调用者透露账户是否存在。
To use Spring Security's @PreAuthorize alternative, first enable method security with @EnableMethodSecurity on a Spring @Configuration class; Spring Boot's security starter does not enable it by default. The following example applies to a Spring-managed component and assumes an application-provided documentAuthorizationService that checks ownership:
要使用 Spring Security 的 @PreAuthorize 选项,首先在 Spring @Configuration 类上启用方法安全 @EnableMethodSecurity;Spring Boot 的安全启动器默认不启用它。以下示例适用于 Spring 管理的组件,并假设提供了一个应用程序提供的 documentAuthorizationService 以检查所有权:
@PreAuthorize("@documentAuthorizationService.isOwner(#id, authentication.name)")
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
return documentRepository.findById(id).orElseThrow();
}
References / 参考¶
- CWE-639: Authorization Bypass Through User-Controlled Key) (CWE-639:通过用户控制的密钥绕过授权)
- OWASP WSTG: Testing for Insecure Direct Object References) (OWASP WSTG:测试不安全直接对象引用)