DjangoORM注入
简介
这篇文章中,分享一些关于django orm相关的技术积累和如果orm注入相关的安全问题讨论。
攻击效果同数据库注入
从Django-Orm开始
开发角度
Django ORM(Object-Relational Mapping)是Django框架中用于处理数据库操作的一种机制。它允许开发者使用Python代码来描述数据库模式和执行数据库查询,进行数据库操作,如创建、读取、更新和删除数据等操作,而不需要直接编写SQL语句。通过ORM,开发者可以更直观和方便地进行数据库操作,同时保持代码的可读性和可维护性。
# 假设需求背景 Python 开发人员想要编写一个博客网站,供人们发布文章,并希望向其应用程序添加搜索功能
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
# models/article.py
from django.db import models
class Article(models.Model):
"""
The data model for Articles
"""
title = models.CharField(max_length=255)
body = models.TextField()
class Meta:
ordering = ["title"]
# serializers/article.py
class ArticleSerializer(serializers.ModelSerializer):
"""
How objects of the Article model are serialized into other data types (e.g. JSON)
"""
class Meta:
model = Article
fields = ('title', 'body')
# views/article.py
class ArticleView(APIView):
"""
Some basic API view that users send requests to for searching for articles
"""
def post(self, request: Request, format=None):
# Returns the search URL parameter if present otherwise it is set to 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)
安全角度
Django ORM通常可以防止SQL注入问题,因为它会自动对查询参数进行适当的转义和处理。不过,如果在使用Django ORM时不小心使用了原生的SQL查询或手动构建了SQL语句,也是有SQL注入的问题,不过这个不是这篇文章讨论的主要方向。仅作概述:
# 安全的查询方式
users = User.objects.filter(username=username)
# 不安全的方式
users = User.objects.raw("SELECT * FROM auth_user WHERE username = '%s'" % username)
# 安全的方式
users = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])
# 安全的方式
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
from django.db import connection
def get_user_by_username(username):
with connection.cursor() as cursor:
# 使用参数化查询防止SQL注入
cursor.execute("SELECT * FROM auth_user WHERE username = %s", [username])
user = cursor.fetchone()
return user
Django-Orm注入
首先创建一个django应用,能够获取book信息
-
假设此时后端开发的领导有了如下的要求:
-
这个时候开发十分容易写出如下代码
盲注获取敏感字段
写在前面,修改部分代码模拟一个靶场环境:
Book.objects.create(title='flagbook', author='flag book', published_date=date(2020, 4, 15), isbn='flag{secret}')
具体
django filter
语法可参考 https://docs.djangoproject.com/en/5.0/ref/models/querysets/#id4
startswith
正确时如下
startswith
错误时如下
泄露的条件总结
-
可以控制
filter
过滤列
-
ORM支持正则、
startswith
类似操作
-
表中存在一个隐藏的敏感字段
多表关联的情况
在 Django 中,
OneToOneField
、
ManyToManyField
和
ForeignKey
是用来定义模型之间关系的字段类型。每种字段类型表示不同的数据库关系。
OneToOneField
OneToOneField
表示一对一关系。一个模型实例与另一个模型实例之间有且仅有一个关联。
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio =
models.TextField()
在这个例子中,每个
UserProfile
实例与一个
User
实例有且只有一个关联。
ManyToManyField
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
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)
在这个例子中,一本书只能有一个出版社,但一个出版社可以出版多本书。
关系总结
-
OneToOneField
: 一对一关系
-
ManyToManyField
: 多对多关系
-
ForeignKey
: 多对一关系
demo演示
我们修改model代码如下
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=100)
address = models.TextField()
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
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)
def __str__(self):
return self.title
class BookDetail(models.Model):
book = models.OneToOneField(Book, on_delete=models.CASCADE)
summary = models.TextField()
number_of_pages = models.IntegerField()
def __str__(self):
return self.book.title
import os
import django
import datetime
import random
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyProject.settings')
django.setup()
from