Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@
- [Objection Tutorial](mobile-pentesting/android-app-pentesting/frida-tutorial/objection-tutorial.md)
- [Google CTF 2018 - Shall We Play a Game?](mobile-pentesting/android-app-pentesting/google-ctf-2018-shall-we-play-a-game.md)
- [In Memory Jni Shellcode Execution](mobile-pentesting/android-app-pentesting/in-memory-jni-shellcode-execution.md)
- [Inputmethodservice Ime Abuse](mobile-pentesting/android-app-pentesting/inputmethodservice-ime-abuse.md)
- [Insecure In App Update Rce](mobile-pentesting/android-app-pentesting/insecure-in-app-update-rce.md)
- [Install Burp Certificate](mobile-pentesting/android-app-pentesting/install-burp-certificate.md)
- [Intent Injection](mobile-pentesting/android-app-pentesting/intent-injection.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ adb shell ime help
- **User/MDM**: allowlist trusted keyboards; block unknown IMEs in managed profiles/devices.
- **App-side (high risk apps)**: prefer phishing-resistant auth (passkeys/biometrics) and avoid relying on “secret text entry” as a security boundary (a malicious IME sits below the app UI).

{{#include ../../banners/hacktricks-training.md}}
102 changes: 102 additions & 0 deletions src/pentesting-web/orm-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,36 @@ From te same post regarding this vector:
- **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 mirrors Django’s `field__operator` DSL, so any handler that lets users control the first argument to `QuerySeter.Filter()` exposes the entire graph of relations:

```go
qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator
```

Requests such as `/search?filter=created_by__user__password__icontains=pbkdf` can pivot through foreign keys exactly like the Django primitives above. Harbor’s `q` helper parsed user input into Beego filters, so low-privileged users could probe secrets by watching list responses:

- `GET /api/v2.0/users?q=password=~$argon2id$` → reveals whether any hash contains `$argon2id$`.
- `GET /api/v2.0/users?q=salt=~abc` → leaks salt substrings.

Counting returned rows, observing pagination metadata, or comparing response lengths gives an oracle to brute-force entire hashes, salts, and 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:

```go
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 deny-lists.

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**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
Expand Down Expand Up @@ -298,6 +328,67 @@ It's also possible to leak all the users abusing some loop back many-to-many rel

Where the `{CONTAINS_LIST}` is a list with 1000 strings to make sure the **response is delayed when the correct leak is found.**

### Type confusion on `where` filters (operator injection)

Prisma’s query API accepts either primitive values or operator objects. When handlers assume the request body contains plain strings but pass them directly to `where`, attackers can smuggle operators into authentication flows and bypass token checks.

```ts
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"}` ⇒ matches every user whose token is not `E`.
- **URL-encoded body** with `extended: true`: `resetToken[not]=E&password=newpass` becomes the same object.
- **Query string** in Express <5 or with extended parsers: `/reset?resetToken[contains]=argon2` leaks 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. Combine this with relational filters (`createdBy`) to pick a victim.

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

<details>
<summary>Microsoft TextFilter helper abused for leaks</summary>

```csharp
IQueryable<T> TextFilter<T>(IQueryable<T> source, string term) {
var stringProperties = typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string));
if (!stringProperties.Any()) { return source; }
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var prm = Expression.Parameter(typeof(T));
var body = stringProperties
.Select(prop => Expression.Call(Expression.Property(prm, prop), containsMethod!, Expression.Constant(term)))
.Aggregate(Expression.OrElse);
return source.Where(Expression.Lambda<Func<T, bool>>(body, prm));
}
```
</details>

Helpers that enumerate every string property and wrap them inside `.Contains(term)` effectively expose passwords, API tokens, salts, and TOTP secrets to any user who can call the endpoint. Directus **CVE-2025-64748** is a real-world example where the `directus_users` search endpoint included `token` and `tfa_secret` in its generated `LIKE` predicates, turning result counts into a 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
```

The mere presence or absence of results (or pagination metadata) lets you binary-search each character according to the database collation. Navigation properties (`CreatedBy/Token`, `CreatedBy/User/Password`) enable relational pivots similar to Django/Beego, so any EDM that exposes sensitive fields or skips per-property deny-lists is an easy target.

Libraries and middleware that translate user strings into ORM operators (e.g., Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers) should be treated as high-risk sinks unless they implement strict field/operator allow-lists.

## **Ransack (Ruby)**

These tricks where [**found in this post**](https://positive.security/blog/ransack-data-exfiltration)**.**
Expand All @@ -324,10 +415,21 @@ GET /posts?q[user_reset_password_token_start]=1

By brute-forcing and potentially relationships it was possible to leak more data from a database.

## Collation-aware leak strategies

String comparisons inherit the database collation, so leak oracles must be designed around how the backend orders characters:

- Default MariaDB/MySQL/SQLite/MSSQL collations are often case-insensitive, so `LIKE`/`=` cannot distinguish `a` from `A`. Use case-sensitive operators (regex/GLOB/BINARY) when the secret’s casing matters.
- Prisma and Entity Framework mirror the database ordering. Collations such as MSSQL’s `SQL_Latin1_General_CP1_CI_AS` place punctuation before digits and letters, so binary-search probes must follow that ordering rather than raw ASCII byte order.
- SQLite’s `LIKE` is case-insensitive unless a custom collation is registered, so Django/Beego leaks may need `__regex` predicates to recover case-sensitive tokens.

Calibrating payloads to the real collation avoids wasted probes and significantly speeds up automated substring/binary-search attacks.

## 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://www.elttam.com/blog/leaking-more-than-you-joined-for/](https://www.elttam.com/blog/leaking-more-than-you-joined-for/)
- [https://positive.security/blog/ransack-data-exfiltration](https://positive.security/blog/ransack-data-exfiltration)

{{#include ../banners/hacktricks-training.md}}
Expand Down