Skip to content

Parameter Fuzzing Guide

Parameter fuzzing discovers hidden, undocumented, or debug parameters accepted by an API endpoint. APILeak's par command injects candidate parameter names from a wordlist and compares responses against a baseline to detect parameters that alter application behavior — even when status codes remain unchanged.

📋 Table of Contents

Overview

🎯 Main Objectives

  • Hidden Parameter Discovery: Find parameters not documented in API specs
  • Injection Point Identification: Detect where user input influences application logic
  • Reflection Detection: Identify parameters whose values are echoed in responses
  • Structural Change Detection: Discover parameters that introduce new JSON fields

🔍 Detection Capabilities

Signal Description
Status code change Response code differs from baseline
Body size change Response body length diverges significantly
Response time change Measurably slower or faster response
Content-type change Different response content type
Reflection Injected sentinel value echoed in body or headers
New JSON fields Response JSON contains keys absent from baseline

⚡ Key Features

  • Unique per-candidate sentinel values (≥16 alphanumeric chars) for precise reflection detection
  • Optional hit confirmation to reduce false positives
  • Request budget to bound scan cost
  • Method-based injection point selection (query vs body)
  • Repeatable wordlists with merge and deduplication
  • Full request-context support (headers, cookies, auth)
  • Response matchers/filters for finding selection
  • Machine-readable output (CSV, JSONL)

How It Works

┌─────────────────────────────────────────────────────┐
│                  par workflow                         │
├─────────────────────────────────────────────────────┤
│ 1. Capture baseline response (no injected params)   │
│ 2. For each candidate parameter from wordlist:      │
│    a. Generate unique sentinel value                 │
│    b. Inject into query string or request body       │
│    c. Compare response against baseline             │
│    d. Check: status, size, time, type, reflection,  │
│       new JSON fields                               │
│    e. If difference detected → candidate finding    │
│ 3. (Optional) Confirm hits with N retests           │
│ 4. Apply matchers/filters to findings               │
│ 5. Report results                                   │
└─────────────────────────────────────────────────────┘

The injection point is determined by --methods: - GET, DELETE → inject into query string (?param=sentinel) - POST, PUT, PATCH → inject into request body (JSON, form-encoded, or XML)

par vs dir: parameter name discovery vs positional value fuzzing

These two commands solve different problems:

par (parameter fuzzing) dir (directory/endpoint fuzzing)
Primary goal Discover unknown parameter names Discover hidden paths/endpoints
Default target URL Plain base URL: https://api.example.com/api/v1/users Plain base URL or marker URL: https://api.example.com/FUZZ
What the wordlist supplies Candidate parameter names (each injected as ?name=<sentinel> or in the request body) Candidate path segments appended to the base URL, or values at each marker position
Marker mode ✅ Supported — --fuzz-keyword (default FUZZ), --fuzz-mode (clusterbomb/pitchfork). In marker mode, markers in the URL are swept with wordlist values rather than parameter names being probed ✅ Full support — same --fuzz-keyword / --fuzz-mode options
Baseline comparison ✅ Compares each response against a no-parameter baseline to detect behavioral changes ❌ No baseline comparison — discovery relies on status codes and response classification
Detection signals Reflection, new JSON fields, status/size/time/content-type changes Status code, response size, endpoint classification

In marker mode, par sweeps the marker positions in the URL with candidate values from --wordlist — this is the same positional-fuzzing behavior as dir. In name-discovery mode (no markers in the URL), par injects each wordlist entry as a parameter name and compares responses against a no-parameter baseline.

If you want to fuzz values at a specific URL position without baseline comparison and using directory-discovery classification, use dir marker mode:

# Sweep a known parameter VALUE with dir marker mode
python apileaks.py dir --target "https://api.example.com/api/v1/?id=FUZZ" --wordlist ids.txt

See Positional Fuzz Markers for the full dir marker workflow.

Basic Examples

Simple parameter fuzzing with minimal configuration.

# Discover hidden parameters on an endpoint using the default wordlist
python apileaks.py par --target https://api.example.com/users/123

# Use a custom wordlist
python apileaks.py par \
  --target https://api.example.com/api/v1/products \
  --wordlist wordlists/parameters.txt

# Fuzz with authentication (JWT)
python apileaks.py par \
  --target https://api.example.com/api/v1/account \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Fuzz only POST body parameters
python apileaks.py par \
  --target https://api.example.com/api/v1/orders \
  --methods POST

# Limit request volume
python apileaks.py par \
  --target https://api.example.com/search \
  --max-requests 500

Intermediate Examples

Adding authentication, resilience, confirmation, and targeted detection.

Request Context

# Custom headers and cookie for an authenticated session
python apileaks.py par \
  --target https://api.example.com/api/v1/profile \
  -H "X-API-Key: sk_live_abc123" \
  -H "X-Tenant-ID: acme" \
  --cookie "session=eyJpZCI6MTIzfQ=="

# Basic auth with a gentle rate limit
python apileaks.py par \
  --target https://internal-api.example.com/admin/config \
  --basic-auth admin:s3cr3t \
  --rate-limit 5

# JWT auth with custom headers
python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "X-Request-ID: pentest-2024"

Hit Confirmation (False Positive Reduction)

# Confirm each candidate finding with 3 retests — only report stable hits
python apileaks.py par \
  --target https://api.example.com/api/v1/search \
  --confirm-hits 3

# Confirmation with a request budget so the total scan stays bounded
python apileaks.py par \
  --target https://api.example.com/api/v1/search \
  --confirm-hits 2 \
  --max-requests 1000

Method and Injection Point Control

# Fuzz only query parameters (GET and DELETE methods)
python apileaks.py par \
  --target https://api.example.com/api/v1/items \
  --methods GET,DELETE

# Fuzz only body parameters (POST and PUT methods)
python apileaks.py par \
  --target https://api.example.com/api/v1/orders \
  --methods POST,PUT

# Fuzz both query and body injection points
python apileaks.py par \
  --target https://api.example.com/api/v1/resources \
  --methods GET,POST,PUT,PATCH

Multiple Wordlists

# Merge two wordlists (duplicates removed automatically)
python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --wordlist wordlists/parameters.txt \
  --wordlist wordlists/debug_params.txt

# Read candidates from stdin (pipe from another tool)
cat custom_params.txt | python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --wordlist -

# Combine stdin with a file-based wordlist
cat discovered_params.txt | python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --wordlist - \
  --wordlist wordlists/parameters.txt

Resilience and Concurrency

# Slow target: increase timeout and add retries
python apileaks.py par \
  --target https://slow-api.example.com/endpoint \
  --timeout 30 \
  --retries 3

# High concurrency for fast targets
python apileaks.py par \
  --target https://fast-api.example.com/search \
  --concurrency 50 \
  --max-requests 5000

# Conservative pacing against rate-limited APIs
python apileaks.py par \
  --target https://api.example.com/api/v1/data \
  --rate-limit 3 \
  --timeout 15 \
  --retries 2

Advanced Examples

Combining multiple capabilities for thorough and targeted parameter discovery.

# Comprehensive parameter discovery with all options
python apileaks.py par \
  --target https://api.example.com/api/v1/users/me \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "X-Correlation-ID: pentest-run-001" \
  --wordlist wordlists/parameters.txt \
  --wordlist wordlists/admin_params.txt \
  --methods GET,POST \
  --confirm-hits 3 \
  --max-requests 2000 \
  --rate-limit 10 \
  --timeout 20 \
  --retries 2 \
  --concurrency 25 \
  --output-format jsonl \
  --output-file reports/par_findings.jsonl

Response Matchers and Filters

# Only report findings with responses larger than 200 bytes
python apileaks.py par \
  --target https://api.example.com/api/v1/search \
  --match-size ">200"

# Exclude false positives matching "not found" text
python apileaks.py par \
  --target https://api.example.com/api/v1/items \
  --filter-regex "not found|error"

# Combine matchers and filters: keep large responses, exclude error patterns
python apileaks.py par \
  --target https://api.example.com/api/v1/data \
  --match-size ">100" \
  --match-words ">10" \
  --filter-regex "404|invalid" \
  --filter-size "<50"

# Time-based detection: find parameters that make the app noticeably slower
python apileaks.py par \
  --target https://api.example.com/api/v1/query \
  --match-time ">2.0"

TLS and Transport

# Mutual TLS (client certificate) against an internal API
python apileaks.py par \
  --target https://internal-api.example.com/admin \
  --client-cert certs/client.pem \
  --ca-bundle certs/internal-ca.pem \
  --basic-auth admin:pass

# DNS resolution override (test against a staging backend)
python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --resolve api.example.com:10.0.1.50

# Route through an intercepting proxy (Burp/Caido)
python apileaks.py par \
  --target https://api.example.com/api/v1/orders \
  --proxy http://127.0.0.1:8080 \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Machine-Readable Output

# JSONL output for pipeline consumption
python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --output-format jsonl \
  --output-file reports/parameters.jsonl

# CSV output for spreadsheet analysis
python apileaks.py par \
  --target https://api.example.com/api/v1/products \
  --output-format csv \
  --output-file reports/parameters.csv

# JSONL with full detection context
python apileaks.py par \
  --target https://api.example.com/api/v1/search \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  --confirm-hits 2 \
  --output-format jsonl \
  --output-file reports/confirmed_params.jsonl

Marker Mode (positional value fuzzing)

When the target URL contains the --fuzz-keyword token, par switches to Marker_Mode and sweeps each marked position with values from --wordlist (instead of probing candidate parameter names).

# Single marker: sweep a path segment with version values
python apileaks.py par \
  --target "https://api.example.com/FUZZ/users" \
  --wordlist versions.txt

# Two markers, cartesian product (clusterbomb — every combination)
python apileaks.py par \
  --target "https://api.example.com/FUZZ/users/FUZZ" \
  --fuzz-mode clusterbomb \
  --wordlist versions.txt \
  --wordlist user_ids.txt

# Two markers paired position-by-position (pitchfork)
python apileaks.py par \
  --target "https://api.example.com/__MARKER__/items/__MARKER__" \
  --fuzz-keyword __MARKER__ \
  --fuzz-mode pitchfork \
  --wordlist prefixes.txt \
  --wordlist suffixes.txt

Per-marker wordlist association. In Marker_Mode the repeatable --wordlist values are associated with markers left-to-right (first --wordlist → first marker). If fewer wordlists than markers are supplied, the last wordlist fills the remaining markers. More wordlists than markers is rejected with an error.

Validation. These configurations are rejected before any request: - Empty or whitespace-only --fuzz-keyword - --fuzz-keyword or --fuzz-mode supplied explicitly but the URL contains no occurrence of the keyword - More --wordlist sources than markers - --fuzz-mode pitchfork with an empty associated wordlist

WAF Evasion

# Random user agent rotation with conservative pacing
python apileaks.py par \
  --target https://protected-api.example.com/endpoint \
  --user-agent-random \
  --rate-limit 3 \
  --timeout 20

# Custom user agent string
python apileaks.py par \
  --target https://api.example.com/api/v1/data \
  --user-agent-custom "Mozilla/5.0 (compatible; Googlebot/2.1)"

# User agent rotation from file
python apileaks.py par \
  --target https://api.example.com/api/v1/users \
  --user-agent-file wordlists/user_agents.txt \
  --rate-limit 5

CI/CD Pipeline Integration

# CI-safe parameter scan: bounded budget, machine output, no interactive prompts
python apileaks.py par \
  --target "${API_ENDPOINT}" \
  --jwt "${JWT_TOKEN}" \
  --wordlist wordlists/parameters.txt \
  --methods GET,POST \
  --confirm-hits 2 \
  --max-requests 1000 \
  --timeout 15 \
  --retries 2 \
  --match-size ">100" \
  --filter-regex "Not Found" \
  --output-format jsonl \
  --output-file reports/par_ci.jsonl \
  --no-banner

# Quick smoke test in a pipeline (small budget, fast)
python apileaks.py par \
  --target "${API_ENDPOINT}" \
  --jwt "${JWT_TOKEN}" \
  --max-requests 200 \
  --output-format csv \
  --output-file reports/par_smoke.csv \
  --no-banner

Real-World Scenarios

# E-commerce: find debug/admin parameters on the checkout endpoint
python apileaks.py par \
  --target https://shop.example.com/api/checkout \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "X-Cart-ID: test-cart-001" \
  --wordlist wordlists/parameters.txt \
  --wordlist wordlists/ecommerce_params.txt \
  --methods POST \
  --confirm-hits 3 \
  --max-requests 1500 \
  --rate-limit 8 \
  --output-format jsonl \
  --output-file reports/checkout_params.jsonl

# Banking API: conservative testing with mutual TLS
python apileaks.py par \
  --target https://api.bank.example.com/v1/accounts/me \
  --client-cert certs/pentest-client.pem \
  --ca-bundle certs/bank-ca.pem \
  --basic-auth auditor:securepass \
  --wordlist wordlists/parameters.txt \
  --confirm-hits 3 \
  --max-requests 500 \
  --rate-limit 2 \
  --timeout 30 \
  --output-format jsonl \
  --output-file reports/bank_params.jsonl

# GraphQL endpoint: find hidden query parameters alongside the standard body
python apileaks.py par \
  --target https://api.example.com/graphql \
  --methods GET,POST \
  --wordlist wordlists/graphql_params.txt \
  --wordlist wordlists/parameters.txt \
  -H "Content-Type: application/json" \
  --jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  --match-size ">500" \
  --confirm-hits 2

# Microservices: scan multiple internal services behind mTLS
for svc in users orders inventory; do
  python apileaks.py par \
    --target "https://${svc}-service.internal.example.com/api/v1" \
    --client-cert certs/scanner.pem \
    --ca-bundle certs/internal-ca.pem \
    --wordlist wordlists/parameters.txt \
    --max-requests 800 \
    --rate-limit 10 \
    --output-format jsonl \
    --output-file "reports/par_${svc}.jsonl" \
    --no-banner
done

Detection Signals

Each reported finding includes the detection signal(s) that triggered it:

Reflection

A unique sentinel value injected as the parameter value was echoed back in the response body or headers. This strongly indicates the parameter is processed by the application.

Finding fields: - detection_signal: "reflection" - reflection_location: "body" or "header"

New JSON Fields

The test response contained top-level JSON keys that were absent from the baseline response. This indicates the parameter unlocks additional data in the response.

Finding fields: - detection_signal: "new_json_field" - new_json_fields: ["field1", "field2"]

Status / Size / Time / Content-Type

Classic difference signals: the response status code, body size, response time, or content type diverged from the baseline.

Finding fields: - detection_signal: "status_code" / "body_size" / "response_time" / "content_type"

Multiple Signals

A single finding can carry multiple detection signals when several indicators fire simultaneously.

Finding fields: - detection_signals: ["reflection:body", "new_json_field"]

Configuration Reference

CLI Options

Option Description Default
--target, -t Target URL (required) —
--wordlist, -w Wordlist file (repeatable, merged/deduped) wordlists/parameters.txt
--fuzz-keyword Literal token marking positions to fuzz in the URL. When present, activates Marker_Mode FUZZ
--fuzz-mode Combination strategy for multiple markers: clusterbomb (cartesian product) or pitchfork (index-wise zip) clusterbomb
--methods HTTP methods to test (comma-separated) GET,POST
--confirm-hits Retest count for hit confirmation (≥1) Off
--max-requests Request budget (≥1) Unbounded
--rate-limit Requests per second 10
--timeout Per-request timeout in seconds (>0) Config default
--retries Retry count for failed requests (≥0) Config default
--concurrency Max in-flight requests (≥1) Config default
-H, --header Custom header (repeatable, Name: Value) —
--cookie Cookie value —
--basic-auth Basic auth (user:pass) —
--jwt JWT bearer token —
--client-cert Client TLS certificate path —
--ca-bundle Custom CA bundle path —
--resolve DNS override (host:ip) —
--match-size Keep findings by size (e.g., >100) —
--match-words Keep findings by word count —
--match-lines Keep findings by line count —
--match-regex Keep findings matching regex —
--match-time Keep findings by response time —
--filter-size Exclude findings by size —
--filter-words Exclude findings by word count —
--filter-lines Exclude findings by line count —
--filter-regex Exclude findings matching regex —
--filter-time Exclude findings by response time —
--output-format Machine output format (csv, jsonl) —
--output-file Machine output file path —
--output, -o Report filename prefix Auto
--proxy HTTP/SOCKS proxy URL —
--user-agent-random Random UA rotation Off
--user-agent-custom Fixed custom UA string —
--user-agent-file UA rotation from file —
--detect-framework Enable framework detection Off
--status-code Display filter by status code All
--no-banner Suppress startup banner Off

YAML Configuration

Parameter fuzzing can also be driven via a YAML config file:

target:
  base_url: "https://api.example.com/api/v1/users"
  timeout: 20

fuzzing:
  parameters:
    enabled: true
    methods: ["GET", "POST"]
    confirm_hits: 2
    max_requests: 1000
    query_wordlist: "wordlists/parameters.txt"
    body_wordlist: "wordlists/body_params.txt"
    boundary_testing: true
  endpoints:
    enabled: false
  concurrency: 25
  retries: 2

authentication:
  contexts:
    - name: "tester"
      type: "bearer"
      token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

rate_limiting:
  requests_per_second: 10

Results Interpretation

Understanding Findings

Each finding represents a candidate parameter that produced a detectable response difference:

Field Meaning
parameter The candidate parameter name
detection_signal Primary signal that triggered the finding
detection_signals All signals that fired (may be multiple)
reflection_location Where the sentinel was reflected (body/header)
new_json_fields List of new JSON keys that appeared
confirmation_status confirmed / excluded_failed_retest / null
status_code Response status code from the test request
response_size Response body size
response_time Response time in seconds

Prioritizing Results

  1. High confidence — Reflection findings with hit confirmation: the sentinel value was echoed and the behavior reproduced across retests.
  2. Medium confidence — New JSON field findings: the parameter unlocks additional response data, suggesting it is processed server-side.
  3. Lower confidence — Status code or body size changes without confirmation: may indicate a real parameter but can also be transient noise (use --confirm-hits to filter).

SSRF-Candidate Escalation

When par discovers a query parameter whose name contains a URL-carrying keyword — url, uri, host, endpoint, target, webhook, callback, redirect, src, feed, imageUrl, etc. — the finding is automatically escalated:

Without keyword match With keyword match (url, callback, webhook, …)
Severity: INFO Severity: MEDIUM
OWASP: — OWASP: API7 (SSRF)
Generic recommendation Recommendation includes SSRF test payload

This means par acts as a first-phase SSRF reconnaissance step: it discovers which URL-accepting parameters exist, and marks them for follow-up with owasp ssrf.

Example finding evidence for an escalated parameter:

Query parameter 'url' discovered - response differs from baseline.
Parameter name suggests a URL-carrying field — potential SSRF attack surface.
Test manually with internal target payloads (e.g. ?url=http://127.0.0.1/).

Recommended follow-up workflow:

# Step 1: discover URL-accepting parameters
python apileaks.py par \
  --target https://api.example.com/v1/proxy \
  --methods GET

# Step 2: exploit confirmed URL params with SSRF probes
python apileaks.py owasp ssrf \
  --target https://api.example.com \
  --openapi /tmp/spec.json \
  --ssrf-internal-targets 169.254.169.254 \
  --ssrf-body-methods POST

Example JSONL Output

{"parameter":"debug","detection_signal":"reflection","detection_signals":["reflection:body"],"reflection_location":"body","confirmation_status":"confirmed","status_code":200,"response_size":1842}
{"parameter":"admin","detection_signal":"new_json_field","detection_signals":["new_json_field"],"new_json_fields":["is_admin","permissions"],"confirmation_status":"confirmed","status_code":200,"response_size":2105}
{"parameter":"verbose","detection_signal":"body_size","detection_signals":["body_size"],"confirmation_status":null,"status_code":200,"response_size":4521}

CI/CD Integration

GitHub Actions

name: Parameter Fuzzing
on: [pull_request]

jobs:
  par-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt

      - name: Parameter fuzzing
        env:
          API_ENDPOINT: ${{ secrets.STAGING_API_URL }}
          JWT_TOKEN: ${{ secrets.STAGING_JWT }}
        run: |
          python apileaks.py par \
            --target "${API_ENDPOINT}/api/v1/users" \
            --jwt "${JWT_TOKEN}" \
            --methods GET,POST \
            --confirm-hits 2 \
            --max-requests 1000 \
            --rate-limit 10 \
            --match-size ">100" \
            --filter-regex "Not Found" \
            --output-format jsonl \
            --output-file reports/par_findings.jsonl \
            --no-banner

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: par-findings
          path: reports/par_findings.jsonl

GitLab CI

par-scan:
  stage: security
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - python apileaks.py par
        --target "${API_ENDPOINT}/api/v1/users"
        --jwt "${JWT_TOKEN}"
        --confirm-hits 2
        --max-requests 1000
        --output-format jsonl
        --output-file reports/par_findings.jsonl
        --no-banner
  artifacts:
    paths:
      - reports/
  only:
    - merge_requests

Troubleshooting

Common Issues

No parameters tested (parameters_tested = 0)

This means the fuzzing engine was unable to reach the target or build the fuzzer.

# Verify target is reachable
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/endpoint

# Try with increased timeout
python apileaks.py par \
  --target https://api.example.com/endpoint \
  --timeout 30 \
  --retries 3

Too many false positives

Unstable responses cause noise. Enable hit confirmation:

python apileaks.py par \
  --target https://api.example.com/unstable-endpoint \
  --confirm-hits 3 \
  --filter-size "<50"

Budget exhausted before completing

Increase the budget or reduce the wordlist:

# Larger budget
python apileaks.py par \
  --target https://api.example.com/endpoint \
  --max-requests 5000

# Or use a smaller, targeted wordlist
python apileaks.py par \
  --target https://api.example.com/endpoint \
  --wordlist wordlists/top_100_params.txt

Validation errors before any request

The CLI validates all options before issuing requests. Common causes:

Error Fix
--methods empty or unsupported Provide at least one of: GET, POST, PUT, PATCH, DELETE
--basic-auth format Use user:pass format (colon required)
--basic-auth + --jwt These are mutually exclusive; choose one
--header missing colon Format must be Name: Value
--client-cert / --ca-bundle unreadable Verify file path and permissions
--resolve malformed Format must be host:ip
--max-requests < 1 Must be a positive integer
--confirm-hits < 1 Must be a positive integer
Invalid matcher/filter expression Check regex syntax or numeric bounds

WAF blocking requests

# Conservative approach: slow rate, legitimate user agent
python apileaks.py par \
  --target https://protected-api.example.com/endpoint \
  --user-agent-custom "Mozilla/5.0 (compatible; Googlebot/2.1)" \
  --rate-limit 2 \
  --timeout 20

For the full CLI reference, see the CLI Reference. For directory fuzzing, see Usage Examples.