DjangoORM注入分享

embedded/2024/12/22 19:26:24/

DjangoORM注入

简介

​ 这篇文章中,分享一些关于django orm相关的技术积累和如果orm注入相关的安全问题讨论。

​ 攻击效果同数据库注入

从Django-Orm开始

开发角度

​ Django ORM(Object-Relational Mapping)是Django框架中用于处理数据库操作的一种机制。它允许开发者使用Python代码来描述数据库模式和执行数据库查询,进行数据库操作,如创建、读取、更新和删除数据等操作,而不需要直接编写SQL语句。通过ORM,开发者可以更直观和方便地进行数据库操作,同时保持代码的可读性和可维护性。

  • 如果没有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
  • 换成ORM模式开发如下
# models/article.py
from django.db import modelsclass 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 = Articlefields = ('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 Nonesearch_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注入的问题,不过这个不是这篇文章讨论的主要方向。仅作概述:

  • 使用ORM的过滤器方法: 始终使用Django ORM的过滤器方法,而不是手动构建SQL查询。例如:
# 安全的查询方式
users = User.objects.filter(username=username)
  • 避免使用raw()方法: raw()方法允许你编写原生SQL查询,但如果不正确处理输入,可能会导致SQL注入。
# 不安全的方式
users = User.objects.raw("SELECT * FROM auth_user WHERE username = '%s'" % username)# 安全的方式
users = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])
  • 使用Django的QuerySet API: 尽量避免使用低级别的数据库API,Django的QuerySet API提供了足够的功能来执行大多数查询,而不需要直接编写SQL
# 安全的方式
users = User.objects.filter(email__icontains='example.com')
  • 使用参数化查询: 如果必须使用自定义的SQL查询,确保使用参数化查询。
from django.db import connectiondef 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 connectiondef 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信息

  • 此时的view逻辑为:

  • 假设此时后端开发的领导有了如下的要求:
    • 需要一个强大的 API 来允许用户按 book 模型中的任何字段进行过滤
    • 后续会不断新增表中的字段,并且希望 API 无需修改任何代码即可兼容这些更改
    • 任务十分紧急
    • ....

  • 这个时候开发十分容易写出如下代码

盲注获取敏感字段

写在前面,修改部分代码模拟一个靶场环境:

  • 新增flagbook,其中isbn为敏感字段flag,真是环境flag可能为密码、手机号、token等敏感字段

Book.objects.create(title='flagbook', author='flag book', published_date=date(2020, 4, 15), isbn='flag{secret}')
  • 修改后端view代码,不返回isbn字段&调整下代码

  • 通过django filter startwith 进行注入获取flag

具体 django filter语法可参考 https://docs.djangoproject.com/en/5.0/ref/models/querysets/#id4

  • 基础语法,查询flagbook数据

  • 盲注获取flag poc

startswith正确时如下

startswith错误时如下

  • 至此,我们就可以写脚本获取完整的flag

泄露的条件总结

  • 可以控制filter过滤列
  • ORM支持正则、startswith类似操作
  • 表中存在一个隐藏的敏感字段

多表关联的情况

在 Django 中,OneToOneFieldManyToManyField 和 ForeignKey 是用来定义模型之间关系的字段类型。每种字段类型表示不同的数据库关系。

OneToOneField

OneToOneField 表示一对一关系。一个模型实例与另一个模型实例之间有且仅有一个关联。

from django.db import modelsclass 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 modelsclass Publisher(models.Model):name = models.CharField(max_length=100)address = models.TextField()def __str__(self):return self.nameclass Category(models.Model):name = models.CharField(max_length=100)def __str__(self):return self.nameclass 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.titleclass 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
  • demo数据如下
import os
import django
import datetime
import randomos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyProject.settings')
django.setup()from BookStore.models import Book, Publisher, Category, BookDetail# 清空旧数据
Publisher.objects.all().delete()
Category.objects.all().delete()
Book.objects.all().delete()
BookDetail.objects.all().delete()# 创建 Publisher 示例数据
publishers = [Publisher.objects.create(name=f'Publisher {i}', address=f'{i} Main St') for i in range(1, 6)
]# 创建 Category 示例数据
categories = [Category.objects.create(name=f'Category {i}') for i in range(1, 6)
]# 创建 Book 示例数据
books = [Book.objects.create(title=f'Book {i}',author=f'Author {i}',published_date=datetime.date(2021, 1, i),isbn=f'{1234567890123 + i}',publisher=random.choice(publishers)) for i in range(1, 6)
]# 添加 Book 到 Category
for book in books:book.categories.add(*random.sample(categories, k=2))  # 随机选择两个分类# 创建 BookDetail 示例数据
for book in books:BookDetail.objects.create(book=book,summary=f'This is the summary for {book.title}.',number_of_pages=random.randint(100, 500))print("Demo data created successfully.")
  • 依旧是这个接口逻辑不变

  • 一对一的方式,通过bookdetail关联 book 的isbn列数据包

  • 多对一的方式,通过book 关联 publishers 的address地址列数据包

  • 多对多的方式,通过book 关联 Category的name列数据包

写在最后

​ 其余的一点思考:created_by__user__password__regex 类似这种会不会造成数据库redos攻击!因为之前学习过redos,答案很明显:几乎不大可能会。数据库的正则引擎为有限状态向量机。https://xz.aliyun.com/t/14653?


http://www.ppmy.cn/embedded/93266.html

相关文章

工程师 - 版本管理工具TLIB

TLIB 是 Burton Systems Software 开发的版本控制工具,主要用于管理软件开发项目中的源代码和其他文件。十多年来,它一直是配置管理领域的著名工具,具有强大的性能和广泛的可配置性。 TLIB is a version control tool developed by Burton Sy…

小智常见报表-自由报表

概述 自由报表:具有自由设计、修改、完善的能力的报表。 应用场景 如下图所示,简单展示数据 示例说明 数据准备 在数据面板中添加数据集,可选择Json数据集和API服务数据集。Json数据集输入如下图所示: [{"姓名"…

如何用 CocosCreator 对接抖音小游戏的侧边栏复访

前言 最近小游戏的软著下来了,用 CocosCreator 做的游戏也完成了 1.0 版本。而当我打包成抖音小游戏进行提交时,还没到初审就给拒了,因为还有一个机审,机器检测到代码中没有接入 “侧边栏复访功能”。这个我还真不知道&#xff0…

magic-api相关应用与配置

目录 项目启动 工具:IDEA 运行项目 关于配置 项目启动 工具:IDEA 新建——》项目——》导入——》运行 运行项目 http://localhost:9999/magic/web/index.htmlhttp://localhost:9999/magic/web/index.html 关于配置 配置多数据源 在线配置多数据…

PyTorch安装与简介

安装PyTorch:使用PIP的方法比较简单 CPU版本安装:pip install torch1.3.0cpu torchvision0.4.1cpu -f https://download.pytorch.org/whl/torch_stable.html GPU版本安装:pip install torch1.3.0 torchvision0.4.1 -f https://download.pyt…

Linux学习笔记:基础硬件知识(个人复习版)

1、目前在个人计算机中常见的硬盘与主板的连接接口有哪两个? 答:有早期的IDE 接口和最近的 SATA 接口。IDE是老式设备常用接口,SATA是现在新式设备用的接口。具体区别参考:硬盘IDE和SATA接口_ide接口-CSDN博客 NVME接口类型&…

算法【滑动窗口】

滑动窗口指的是维持左、右边界都不回退的一段范围,来求解很多子数组(串)的相关问题。 滑动窗口的关键是找到范围和答案指标之间的单调性关系(类似贪心)。 滑动过程:滑动窗口可以用简单变量或者结构来维护…

Docker最佳实践(七):安装MinIO文件服务器

大家好,欢迎各位工友。 Minio是一个开源免费的高性能对象存储服务器,专为大规模数据集和高并发访问而设计。它具有出色的读写性能和低延迟,可以满足对数据速度和效率要求较高的应用场景。本篇呢我们就来演示一下如何在Docker中搭建Minio容器&…