Back to Articles

Building Custom Query String Fuzzers: Inside qsfuzz's Rule-Based Vulnerability Detection

[ View on GitHub ]

Building Custom Query String Fuzzers: Inside qsfuzz's Rule-Based Vulnerability Detection

Hook

Most fuzzing tools spray hundreds of payloads at every parameter simultaneously, breaking applications before finding vulnerabilities. qsfuzz takes the opposite approach: one parameter at a time, with intelligent baseline comparisons that distinguish real SQL injection from application quirks.

Context

Web application fuzzing has always faced a fundamental tension: spray too many payloads and you overwhelm the target or trigger rate limits; test too conservatively and you miss vulnerabilities. Traditional tools like Burp Intruder excel at brute force but lack the context-awareness to distinguish a genuine SQL injection from an application that simply dislikes apostrophes in user input.

This problem becomes acute in bug bounty hunting, where you're testing third-party applications without access to logs or source code. You need a tool that can make intelligent decisions about what constitutes suspicious behavior—one that understands the difference between "this parameter injection caused a 500 error" and "this parameter injection caused a DIFFERENT 500 error than the baseline." qsfuzz emerged from this need, offering a YAML-based rule system that lets security researchers encode their testing hypotheses and compare injected responses against clean baselines to filter false positives.

Technical Insight

Per-Parameter Testing

Payloads + Expectations

Parsed URLs

Store Response

One-at-a-time Injection

Response

Status/Body/Headers

Match Decision

Optional

stdin URLs

URL Parser

YAML Rule Files

Rule Loader

Fuzzing Engine

Baseline Request

Heuristic Comparator

Parameter Injector

HTTP Client

Multi-Criteria Matcher

Results Aggregator

stdout Output

Slack Notifier

System architecture — auto-generated

qsfuzz's architecture centers on three core concepts: one-at-a-time parameter replacement, YAML rule definitions, and heuristic baseline comparisons. Unlike traditional fuzzers that might replace all parameters simultaneously (turning ?user=bob&role=admin into ?user=<script>&role=<script>), qsfuzz tests each parameter independently. This design decision prevents cascading failures—when one parameter breaks, dependent parameters remain functional, letting you isolate which specific input triggers vulnerable behavior.

The rule system uses YAML files that define both injection payloads and expected response characteristics. Here's what a SQL injection detection rule looks like:

name: "SQL Injection - Error-Based"
payloads:
  - "'"
  - "')"
  - "' OR '1'='1"
  - "' AND 1=CONVERT(int, (SELECT @@version))--"
expectations:
  status_codes:
    - 500
    - 400
  body_content:
    - "SQL syntax"
    - "mysql_fetch"
    - "ORA-01756"
    - "Microsoft OLE DB Provider"
  headers:
    - "X-Database-Error"

The matching logic requires ALL expectation categories present in the rule to be satisfied, but only ONE item within each category needs to match. So this rule triggers if the response has (status 500 OR 400) AND (body contains "SQL syntax" OR "mysql_fetch" OR any other signature) AND (has the X-Database-Error header if that category were uncommented). This multi-dimensional matching reduces false positives compared to simple pattern matching.

Where qsfuzz truly differentiates itself is in heuristic-based testing. Instead of just checking if an injection returns a 500 error, it compares the injected response against a baseline. The tool makes a clean request first, then tests if a single quote produces a different error than a double quote. Here's the conceptual flow:

// Pseudo-code representation of qsfuzz's heuristic logic
baselineResp := makeRequest(url, originalParams)
singleQuoteResp := makeRequest(url, replaceParam("param", "'"))
doubleQuoteResp := makeRequest(url, replaceParam("param", "\""))

// Heuristic: SQL injection often treats quotes differently
if singleQuoteResp.StatusCode == 500 && 
   doubleQuoteResp.StatusCode == baselineResp.StatusCode &&
   singleQuoteResp.Body != doubleQuoteResp.Body {
    reportVulnerability("Potential SQL injection - asymmetric quote handling")
}

This heuristic catches cases where an application crashes on single quotes (due to improper SQL escaping) but handles double quotes gracefully—a strong SQL injection indicator that simple error-based detection would miss among noisy 500 errors.

The tool also supports response length variance matching with a 10% tolerance. When you specify response_length: 1234 in your rule, qsfuzz matches any response within ±123 bytes. This catches vulnerabilities that don't produce error messages but do alter response structure—think blind SQL injection via boolean-based conditions or XXE attacks that exfiltrate data by expanding entity references.

Templating provides basic payload contextualization. You can inject the original parameter value, domain, or path into your payloads using {{original}}, {{domain}}, {{path}}, and {{random}} placeholders:

payloads:
  - "{{original}}' OR '1'='1"
  - "https://{{domain}}/../../etc/passwd"
  - "{{random}}.{{domain}}"

This enables testing for SSRF (Server-Side Request Forgery) with domain-specific callbacks, or appending to existing values rather than replacing them entirely. While described as "rudimentary" in the documentation, this covers the most common contextual fuzzing scenarios without the complexity of a full templating language.

Gotcha

qsfuzz's biggest limitation is its lack of sophistication around HTTP client concerns. There's no visible rate limiting, concurrency control, or session management. If you're testing an application with aggressive WAF rules or authentication requirements, you'll need to wrap qsfuzz in additional tooling or manually throttle input. The tool reads URLs from stdin and writes results to stdout, making it very Unix-pipeline-friendly, but this also means authentication headers must be passed via command-line flags for every request—there's no session persistence or cookie jar management.

The heuristic testing, while conceptually powerful, appears under-documented and potentially under-tested. The README literally cuts off mid-sentence in the heuristics section ("This allows you to use heur"), leaving users to reverse-engineer the logic from source code. Without a visible test suite validating these heuristics across different application behaviors, you're trusting that the baseline comparison logic handles edge cases correctly. For production security assessments, you'd want to validate qsfuzz findings manually rather than relying on its heuristic determinations as definitive.

Finally, this is clearly a tool built for traditional query-string-based web applications. If you're testing modern JSON APIs, GraphQL endpoints, or applications using POST body parameters, you'll need different tooling. The repository shows limited recent activity, suggesting it's in maintenance mode rather than active development—functional for its intended use case but unlikely to expand into new testing paradigms.

Verdict

Use if: You're doing bug bounty hunting or security research on query-string-based web applications and need customizable, YAML-driven fuzzing rules with fine-grained control over detection criteria. The heuristic baseline comparison is particularly valuable if you're tired of wading through false-positive SQL injection reports where the application just doesn't like special characters. It's also ideal if you want a lightweight, pipeline-friendly tool that integrates easily into shell scripts or CI/CD workflows. Skip if: You need production-grade tooling with robust authentication handling, rate limiting, and comprehensive error recovery. Also skip if you're primarily testing modern APIs (GraphQL, REST with JSON bodies) rather than traditional query parameters, or if you require active community support and regular updates. For those scenarios, look at ffuf for general-purpose fuzzing with better performance controls, or nuclei for broader protocol coverage with actively maintained community templates.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/ameenmaali-qsfuzz.svg)](https://starlog.is/api/badge-click/cybersecurity/ameenmaali-qsfuzz)