django-access-review by getsentry/skills
npx skills add https://github.com/getsentry/skills --skill django-access-review通过调查代码库如何回答以下问题来发现访问控制漏洞:
用户 A 能否访问、修改或删除用户 B 的数据?
不要扫描预定义的易受攻击模式。相反:
每个代码库实现授权的方式都不同。你的工作是理解这个特定的实现,然后找出其中的漏洞。
在寻找漏洞之前,先回答关于代码库的以下问题:
研究代码库以找到:
□ 权限检查在何处实现?
- 装饰器? (@login_required, @permission_required, 自定义?)
- 中间件? (TenantMiddleware, AuthorizationMiddleware?)
- 基类? (BaseAPIView, TenantScopedViewSet?)
- 权限类? (DRF permission_classes?)
- 自定义混入类? (OwnershipMixin, TenantMixin?)
□ 查询是如何限定范围的?
- 自定义管理器? (TenantManager, UserScopedManager?)
- get_queryset() 重写?
- 设置查询上下文的中间件?
□ 所有权模型是什么?
- 单一用户所有权? (document.owner_id)
- 组织/租户所有权? (document.organization_id)
- 层级结构? (org -> team -> user -> resource)
- 上下文内的基于角色的? (org admin vs member)
# 查找典型的认证实现方式
grep -rn "permission_classes\|@login_required\|@permission_required" --include="*.py" | head -20
# 查找视图继承的基类
grep -rn "class Base.*View\|class.*Mixin.*:" --include="*.py" | head -20
# 查找自定义管理器
grep -rn "class.*Manager\|def get_queryset" --include="*.py" | head -20
# 查找模型上的所有权字段
grep -rn "owner\|user_id\|organization\|tenant" --include="models.py" | head -30
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在理解授权模型之前,不要继续。
识别处理用户特定数据的端点:
□ 哪些模型包含用户数据?
□ 哪些模型拥有所有权字段 (owner_id, user_id, organization_id)?
□ 哪些模型通过 URL 或请求体中的 ID 进行访问?
为每个资源映射:
对于每个处理用户数据的端点,询问:
“如果我是用户 A,并且我知道用户 B 资源的 ID,我能访问它吗?”
追踪代码来回答这个问题:
1. 资源 ID 从何处进入系统?
- URL 路径: /api/documents/{id}/
- 查询参数: ?document_id=123
- 请求体: {"document_id": 123}
2. 该 ID 在何处用于获取数据?
- 找到 ORM 查询或数据库调用
3. 在 (1) 和 (2) 之间,存在哪些检查?
- 查询是否限定在当前用户?
- 是否有明确的所有权检查?
- 是否有对对象的权限检查?
- 是否有基类或混入类强制执行访问控制?
4. 如果找不到检查,是否有遗漏的检查?
- 检查父类
- 检查中间件
- 检查管理器
- 检查 URL 级别的装饰器
□ 对于列表端点:查询是否过滤到用户的数据,还是返回所有数据?
□ 对于创建端点:谁设置所有者 - 服务器还是请求?
□ 对于批量操作:它们是否限定在用户的数据范围内?
□ 对于相关资源:如果我能访问一个文档,我能访问它的评论吗?
如果文档属于其他人呢?
□ 对于租户/组织资源:组织 A 中的用户能否通过更改 URL 中的 org_id 来访问组织 B 的数据?
选择一个具体的端点并完整地追踪它。
Endpoint: GET /api/documents/{pk}/
1. 找到处理此 URL 的视图
→ DocumentViewSet.retrieve() in api/views.py
2. 检查 DocumentViewSet 继承自什么
→ class DocumentViewSet(viewsets.ModelViewSet)
→ 没有带有授权的自定义基类
3. 检查 permission_classes
→ permission_classes = [IsAuthenticated]
→ 仅检查登录状态,不检查所有权
4. 检查 get_queryset()
→ def get_queryset(self):
→ return Document.objects.all()
→ 返回所有文档!
5. 检查是否有 has_object_permission()
→ 未实现
6. 检查 retrieve() 方法
→ 使用默认方法,该方法调用 get_object()
→ get_object() 使用 get_queryset(),而 get_queryset() 返回所有数据
7. 结论:IDOR - 任何经过身份验证的用户都可以访问任何文档
潜在漏洞指标(进一步调查,不要自动标记):
- get_queryset() 返回 .all() 或过滤时未包含用户
- 直接使用 Model.objects.get(pk=pk) 而查询中不包含所有权
- 敏感操作的 ID 来自请求体
- 权限类检查身份验证但不检查所有权
- 没有 has_object_permission() 且查询集未限定范围
可能安全的模式(但要验证实现):
- get_queryset() 通过 request.user 或用户的组织进行过滤
- 带有 has_object_permission() 的自定义权限类
- 强制执行范围限定的基类
- 自动过滤的管理器
仅报告你通过调查确认的问题。
| 级别 | 含义 | 操作 |
|---|---|---|
| 高 | 追踪了流程,确认不存在检查 | 提供证据进行报告 |
| 中 | 可能存在检查但无法确认 | 注明需要手动验证 |
| 低 | 理论上的,可能已被缓解 | 不报告 |
错误的修复:添加注释说明“调用者必须验证权限” 正确的修复:添加实际验证权限的代码
注释或文档字符串并不能强制执行授权。你建议的修复必须包含实际代码,该代码:
错误的修复建议示例:
def get_resource(resource_id):
# 重要:调用者必须确保用户有权访问此资源
return Resource.objects.get(pk=resource_id)
正确的修复建议示例:
def get_resource(resource_id, user):
resource = Resource.objects.get(pk=resource_id)
if resource.owner_id != user.id:
raise PermissionDenied("Access denied")
return resource
如果你无法确定正确的强制执行机制,请说明这一点 - 但永远不要建议仅通过文档来修复。
## 访问控制审查:[组件]
### 授权模型
[关于此代码库如何处理授权的简要描述]
### 发现
#### [IDOR-001] [标题] (严重性:高/中)
- **位置**: `path/to/file.py:123`
- **置信度**: 高 - 通过代码追踪确认
- **问题**: 用户 A 能访问用户 B 的文档吗?
- **调查**:
1. 追踪 GET /api/documents/{pk}/ 到 DocumentViewSet
2. 检查 get_queryset() - 返回 Document.objects.all()
3. 检查 permission_classes - 仅 IsAuthenticated
4. 检查是否有 has_object_permission() - 未实现
5. 验证没有相关的中间件或基类检查
- **证据**: [显示漏洞的代码片段]
- **影响**: 任何经过身份验证的用户都可以通过 ID 读取任何文档
- **建议的修复**: [强制执行授权的代码 - 不是注释]
### 需要手动验证
[存在授权但无法确认其有效性的问题]
### 未审查的区域
[本次审查未涵盖的端点或流程]
这些是你可能发现的模式 - 不是用于匹配的检查清单。
# 限定到用户
Document.objects.filter(owner=request.user)
# 限定到组织
Document.objects.filter(organization=request.user.organization)
# 使用自定义管理器
Document.objects.for_user(request.user) # 调查此方法的作用
# DRF 权限类
permission_classes = [IsAuthenticated, IsOwner]
# 自定义 has_object_permission
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
# Django 装饰器
@permission_required('app.view_document')
# 手动检查
if document.owner != request.user:
raise PermissionDenied()
# 服务器端(安全)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# 来自请求(需要调查)
serializer.save(**request.data) # request.data 是否包含 owner?
使用此清单来指导你的审查,而不是作为通过/失败的检查清单:
□ 我理解此代码库中授权通常是如何实现的
□ 我已经确定了所有权模型(用户、组织、租户等)
□ 我已经映射了处理用户数据的关键端点
□ 对于每个敏感端点,我已经追踪了流程并询问了:
- ID 从何而来?
- 数据在何处获取?
- 在输入和数据访问之间存在哪些检查?
□ 我已经通过检查父类和中间件验证了我的发现
□ 我只报告了通过调查确认的问题
每周安装量
215
代码库
GitHub 星标数
454
首次出现
2026年2月11日
安全审计
安装于
claude-code189
codex188
gemini-cli188
opencode186
github-copilot185
cursor180
Find access control vulnerabilities by investigating how the codebase answers one question:
Can User A access, modify, or delete User B's data?
Do NOT scan for predefined vulnerable patterns. Instead:
Every codebase implements authorization differently. Your job is to understand this specific implementation, then find gaps.
Before looking for bugs, answer these questions about the codebase:
Research the codebase to find:
□ Where are permission checks implemented?
- Decorators? (@login_required, @permission_required, custom?)
- Middleware? (TenantMiddleware, AuthorizationMiddleware?)
- Base classes? (BaseAPIView, TenantScopedViewSet?)
- Permission classes? (DRF permission_classes?)
- Custom mixins? (OwnershipMixin, TenantMixin?)
□ How are queries scoped?
- Custom managers? (TenantManager, UserScopedManager?)
- get_queryset() overrides?
- Middleware that sets query context?
□ What's the ownership model?
- Single user ownership? (document.owner_id)
- Organization/tenant ownership? (document.organization_id)
- Hierarchical? (org -> team -> user -> resource)
- Role-based within context? (org admin vs member)
# Find how auth is typically done
grep -rn "permission_classes\|@login_required\|@permission_required" --include="*.py" | head -20
# Find base classes that views inherit from
grep -rn "class Base.*View\|class.*Mixin.*:" --include="*.py" | head -20
# Find custom managers
grep -rn "class.*Manager\|def get_queryset" --include="*.py" | head -20
# Find ownership fields on models
grep -rn "owner\|user_id\|organization\|tenant" --include="models.py" | head -30
Do not proceed until you understand the authorization model.
Identify endpoints that handle user-specific data:
□ What models contain user data?
□ Which have ownership fields (owner_id, user_id, organization_id)?
□ Which are accessed via ID in URLs or request bodies?
For each resource, map:
For each endpoint that handles user data, ask:
"If I'm User A and I know the ID of User B's resource, can I access it?"
Trace the code to answer this:
1. Where does the resource ID enter the system?
- URL path: /api/documents/{id}/
- Query param: ?document_id=123
- Request body: {"document_id": 123}
2. Where is that ID used to fetch data?
- Find the ORM query or database call
3. Between (1) and (2), what checks exist?
- Is the query scoped to current user?
- Is there an explicit ownership check?
- Is there a permission check on the object?
- Does a base class or mixin enforce access?
4. If you can't find a check, is there one you missed?
- Check parent classes
- Check middleware
- Check managers
- Check decorators at URL level
□ For list endpoints: Does the query filter to user's data, or return everything?
□ For create endpoints: Who sets the owner - the server or the request?
□ For bulk operations: Are they scoped to user's data?
□ For related resources: If I can access a document, can I access its comments?
What if the document belongs to someone else?
□ For tenant/org resources: Can User in Org A access Org B's data by changing
the org_id in the URL?
Pick a concrete endpoint and trace it completely.
Endpoint: GET /api/documents/{pk}/
1. Find the view handling this URL
→ DocumentViewSet.retrieve() in api/views.py
2. Check what DocumentViewSet inherits from
→ class DocumentViewSet(viewsets.ModelViewSet)
→ No custom base class with authorization
3. Check permission_classes
→ permission_classes = [IsAuthenticated]
→ Only checks login, not ownership
4. Check get_queryset()
→ def get_queryset(self):
→ return Document.objects.all()
→ Returns ALL documents!
5. Check for has_object_permission()
→ Not implemented
6. Check retrieve() method
→ Uses default, which calls get_object()
→ get_object() uses get_queryset(), which returns all
7. Conclusion: IDOR - Any authenticated user can access any document
Potential gap indicators (investigate further, don't auto-flag):
- get_queryset() returns .all() or filters without user
- Direct Model.objects.get(pk=pk) without ownership in query
- ID comes from request body for sensitive operations
- Permission class checks auth but not ownership
- No has_object_permission() and queryset isn't scoped
Likely safe patterns (but verify the implementation):
- get_queryset() filters by request.user or user's org
- Custom permission class with has_object_permission()
- Base class that enforces scoping
- Manager that auto-filters
Only report issues you've confirmed through investigation.
| Level | Meaning | Action |
|---|---|---|
| HIGH | Traced the flow, confirmed no check exists | Report with evidence |
| MEDIUM | Check may exist but couldn't confirm | Note for manual verification |
| LOW | Theoretical, likely mitigated | Do not report |
Bad fix : Adding a comment saying "caller must validate permissions" Good fix : Adding code that actually validates permissions
A comment or docstring does not enforce authorization. Your suggested fix must include actual code that:
Example of a BAD fix suggestion:
def get_resource(resource_id):
# IMPORTANT: Caller must ensure user has access to this resource
return Resource.objects.get(pk=resource_id)
Example of a GOOD fix suggestion:
def get_resource(resource_id, user):
resource = Resource.objects.get(pk=resource_id)
if resource.owner_id != user.id:
raise PermissionDenied("Access denied")
return resource
If you can't determine the right enforcement mechanism, say so - but never suggest documentation as the fix.
## Access Control Review: [Component]
### Authorization Model
[Brief description of how this codebase handles authorization]
### Findings
#### [IDOR-001] [Title] (Severity: High/Medium)
- **Location**: `path/to/file.py:123`
- **Confidence**: High - confirmed through code tracing
- **The Question**: Can User A access User B's documents?
- **Investigation**:
1. Traced GET /api/documents/{pk}/ to DocumentViewSet
2. Checked get_queryset() - returns Document.objects.all()
3. Checked permission_classes - only IsAuthenticated
4. Checked for has_object_permission() - not implemented
5. Verified no relevant middleware or base class checks
- **Evidence**: [Code snippet showing the gap]
- **Impact**: Any authenticated user can read any document by ID
- **Suggested Fix**: [Code that enforces authorization - NOT a comment]
### Needs Manual Verification
[Issues where authorization exists but couldn't confirm effectiveness]
### Areas Not Reviewed
[Endpoints or flows not covered in this review]
These are patterns you might find - not a checklist to match against.
# Scoped to user
Document.objects.filter(owner=request.user)
# Scoped to organization
Document.objects.filter(organization=request.user.organization)
# Using a custom manager
Document.objects.for_user(request.user) # Investigate what this does
# DRF permission classes
permission_classes = [IsAuthenticated, IsOwner]
# Custom has_object_permission
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
# Django decorators
@permission_required('app.view_document')
# Manual checks
if document.owner != request.user:
raise PermissionDenied()
# Server-side (safe)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# From request (investigate)
serializer.save(**request.data) # Does request.data include owner?
Use this to guide your review, not as a pass/fail checklist:
□ I understand how authorization is typically implemented in this codebase
□ I've identified the ownership model (user, org, tenant, etc.)
□ I've mapped the key endpoints that handle user data
□ For each sensitive endpoint, I've traced the flow and asked:
- Where does the ID come from?
- Where is data fetched?
- What checks exist between input and data access?
□ I've verified my findings by checking parent classes and middleware
□ I've only reported issues I've confirmed through investigation
Weekly Installs
215
Repository
GitHub Stars
454
First Seen
Feb 11, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code189
codex188
gemini-cli188
opencode186
github-copilot185
cursor180
Better Auth 最佳实践指南:集成、配置与安全设置完整教程
30,700 周安装