如何防护带参数的Django Rest API免受SQL注入攻击?
防护Django Rest API URL参数免受SQL注入的方案
先排查你可能忽略的注入根源:
- 你是不是错误地使用了参数化查询?比如用ORM.raw时直接用f-string拼接参数,而不是用占位符;或者cursor.execute直接把参数嵌进SQL字符串里,没把参数作为第二个参数传入。这是最常见的失误,哪怕你以为用了参数化,其实根本没生效。
- 有没有在排序、分组这类不支持参数化的场景直接拼用户输入?比如把
request.GET.get('order_by')直接传给order_by(),这种情况Django没法参数化,很容易被注入。 - 是不是用了自定义的SQL拼接逻辑?比如自己写函数生成WHERE条件,没做参数化处理;或者第三方库的查询逻辑存在漏洞。
接下来是具体的防护措施,覆盖所有URL参数场景:
一、严格遵循Django安全查询规范
- 优先用ORM内置方法:
filter()、get()、exclude()这些方法会自动处理参数化,完全避免注入。比如:
不管用户传什么id,Django都会把它作为参数绑定到SQL里,不会直接拼接。# 安全写法 user = User.objects.get(id=request.query_params.get('id')) - 正确使用
raw()查询:必须用占位符,参数单独传入,绝对不能直接拼接字符串:# 正确 user_id = request.query_params.get('id') users = User.objects.raw("SELECT * FROM auth_user WHERE id = %s", [user_id]) # 错误!直接拼接会被注入 # users = User.objects.raw(f"SELECT * FROM auth_user WHERE id = {user_id}") - 正确使用原生cursor:同样要把参数作为第二个参数传给
execute(),不能拼进SQL:from django.db import connection with connection.cursor() as cursor: # 正确 cursor.execute("SELECT * FROM auth_user WHERE id = %s", (user_id,)) # 错误 # cursor.execute(f"SELECT * FROM auth_user WHERE id = {user_id}") row = cursor.fetchone()
二、处理特殊查询场景
- 排序/分组字段校验:如果允许用户指定排序字段(比如
?order_by=username),必须做白名单校验,不能直接用用户输入:
直接传用户输入会导致注入,比如攻击者传allowed_order_fields = ['id', 'username', 'email'] order_by = request.query_params.get('order_by', 'id') # 不在白名单里就用默认值 if order_by not in allowed_order_fields: order_by = 'id' users = User.objects.all().order_by(order_by)order_by=id;DROP TABLE auth_user--。 - IN子句安全处理:如果是多ID查询,用Django的
__in,并先把参数转成正确类型:
不要自己手动拼ids = request.query_params.getlist('ids') # 过滤并转换为整数,避免恶意字符串 try: ids = [int(id_str) for id_str in ids] except ValueError: ids = [] users = User.objects.filter(id__in=ids)IN (x,y,z)这种SQL片段,风险极高。
三、全局加固措施
- 用DRF序列化器做参数校验:对URL参数做类型和格式校验,确保输入符合预期:
这样能提前拦截不符合类型的恶意输入。from rest_framework import serializers class UserQuerySerializer(serializers.Serializer): id = serializers.IntegerField(required=False) order_by = serializers.CharField(required=False, max_length=20) # 在视图中验证参数 serializer = UserQuerySerializer(data=request.query_params) if serializer.is_valid(raise_exception=True): validated_data = serializer.validated_data # 用校验后的参数做查询 user_id = validated_data.get('id') - 限制数据库用户权限:给Django使用的数据库账号分配最小权限,比如只允许对业务表做SELECT、INSERT、UPDATE,禁止DROP、ALTER、CREATE等操作。就算被注入,攻击者也没法破坏数据库结构。
- 开启SQL日志排查:测试环境开启
DEBUG=True,查看Django执行的实际SQL语句,确认参数是否被正确参数化(比如SQL里是%s而不是直接显示用户输入)。 - 定期自测:像你现在这样用SQLmap扫描自己的API,发现问题及时修复,不要等上线后被攻击者利用。
四、快速排查自己的代码
你说用了参数化还被注入,先检查这几个点:
- 所有涉及URL参数的SQL查询,是不是都用了参数化占位符,没有直接拼接字符串?
- 有没有在排序、分组、表名这类无法参数化的地方直接使用用户输入?
- 是不是有自定义的SQL生成逻辑,没做参数化处理?
- 视图里的所有查询(包括辅助查询)都做了安全处理吗?
内容的提问来源于stack exchange,提问作者Monad Wizard
相关产品推荐
相关产品推荐

