DjangoORM注入分享
字数 888 2025-08-22 18:37:22
Django ORM 注入安全研究
1. Django ORM 基础
1.1 ORM 概念与优势
Django ORM (Object-Relational Mapping) 是 Django 框架中用于处理数据库操作的机制,允许开发者使用 Python 代码描述数据库模式和执行查询,无需直接编写 SQL 语句。
传统 SQL 查询示例:
def search_articles(search_term: str) -> list[dict]:
results = []
with get_db_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT title, body FROM articles WHERE title LIKE %s", (f"%{search_term}%",))
rows = cursor.fetchall()
for row in rows:
results.append({
"title": row[0],
"body": row[1]
})
return results
ORM 方式示例:
# models/article.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
class Meta:
ordering = ["title"]
# views/article.py
class ArticleView(APIView):
def post(self, request: Request, format=None):
search_term = request.data.get("search", None)
if search_term is not None:
articles = Article.objects.filter(title__contains=search_term)
else:
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
1.2 ORM 安全特性
Django ORM 通常能防止 SQL 注入,因为它会自动对查询参数进行转义和处理。但需要注意:
-
使用过滤器方法:
# 安全的查询方式 users = User.objects.filter(username=username) -
避免使用 raw() 方法:
# 不安全的方式 users = User.objects.raw("SELECT * FROM auth_user WHERE username = '%s'" % username) # 安全的方式 users = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username]) -
使用 QuerySet API:
# 安全的方式 users = User.objects.filter(email__icontains='example.com') -
参数化查询:
from django.db import connection def get_user_by_username(username): with connection.cursor() as cursor: cursor.execute("SELECT * FROM auth_user WHERE username = %s", [username]) row = cursor.fetchone() return row
2. Django ORM 注入漏洞
2.1 漏洞场景
当开发需要实现"按任意字段过滤"功能时,可能写出不安全代码:
# 不安全的实现方式
query = request.GET.dict()
books = Book.objects.filter(**query)
2.2 漏洞利用示例
靶场环境设置:
Book.objects.create(title='flagbook', author='flag book',
published_date=date(2020, 4, 15),
isbn='flag{secret}')
利用 startswith 进行盲注:
GET /books?title__startswith=f
# 返回数据包含flagbook
GET /books?title__startswith=x
# 不返回flagbook
通过这种方式可以逐步推断出敏感字段的值。
2.3 泄露条件总结
- 可以控制 filter 过滤列
- ORM 支持正则、startswith 等操作
- 表中存在隐藏的敏感字段
3. 多表关联注入
3.1 Django 关系字段类型
-
OneToOneField: 一对一关系
class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) bio = models.TextField() -
ManyToManyField: 多对多关系
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) -
ForeignKey: 多对一关系
class Publisher(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=100) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
3.2 多表注入示例
模型定义:
class Publisher(models.Model):
name = models.CharField(max_length=100)
address = models.TextField()
class Category(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
categories = models.ManyToManyField(Category)
class BookDetail(models.Model):
book = models.OneToOneField(Book, on_delete=models.CASCADE)
summary = models.TextField()
number_of_pages = models.IntegerField()
注入方式:
-
一对一关系:
GET /books?bookdetail__summary__startswith=T -
多对一关系:
GET /books?publisher__address__startswith=1 -
多对多关系:
GET /books?categories__name__startswith=C
4. 防御措施
-
白名单过滤:
ALLOWED_FILTERS = ['title', 'author', 'published_date'] query = {k: v for k, v in request.GET.items() if k in ALLOWED_FILTERS} books = Book.objects.filter(**query) -
限制关联查询深度:
MAX_RELATION_DEPTH = 1 -
敏感字段排除:
class BookSerializer(serializers.ModelSerializer): class Meta: model = Book exclude = ['isbn', 'sensitive_field'] -
输入验证:
from django.core.validators import validate_slug def clean_username(value): validate_slug(value) # 只允许字母、数字、下划线或连字符
5. 其他安全考虑
关于正则表达式攻击(ReDoS)的思考:
- 数据库正则引擎通常使用有限状态机实现,不易受到ReDoS攻击
- 但应用层正则处理仍需注意性能问题
示例:
# 几乎不会造成数据库ReDoS
created_by__user__password__regex=r'^(a+)+$'
6. 总结
Django ORM 虽然提供了良好的SQL注入防护,但不当使用仍可能导致数据泄露。特别是在:
- 允许用户控制过滤条件时
- 使用动态**kwargs参数时
- 存在敏感字段但未正确过滤时
开发者应当:
- 限制可过滤字段
- 控制关联查询深度
- 排除敏感字段
- 实施严格的输入验证