Skill: sunweb3sec/llm-sast-scanner (version 1.3.2)

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

path: references/brute_force.md
content: ---
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')
    [REDACTED_SECRET]'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({ [REDACTED_SECRET]) });
    } 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).


path: references/session_fixation.md
content: ---
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.


path: references/insecure_deserialization.md
content: ---
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


path: references/ssrf.md
content: ---
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.


path: references/business_logic.md
content: ---
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


path: references/verification_code_abuse.md
content: ---
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.


path: references/trust_boundary.md
content: ---
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`.


path: references/weak_crypto_hash.md
content: ---
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.


path: references/ssti.md
content: ---
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`.


path: references/php_security.md
content: ---
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
$[REDACTED_SECRET]);
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


path: references/http_method_tamper.md
content: ---
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


path: references/insecure_cookie.md
content: ---
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.


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector:  ()