Skip to content

Lightfuzz Overhaul (False-positive reductions, False-negative reductions, many new techniques)#2967

Merged
liquidsec merged 59 commits into
devfrom
lightfuzz-improvements-mar-26
Jun 9, 2026
Merged

Lightfuzz Overhaul (False-positive reductions, False-negative reductions, many new techniques)#2967
liquidsec merged 59 commits into
devfrom
lightfuzz-improvements-mar-26

Conversation

@liquidsec

@liquidsec liquidsec commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Systematic reduction of both false positives and false negatives in lightfuzz, plus new detection techniques across multiple submodules. FN work driven by a structured "false-negative hunt" — auditing each submodule, building gap-filler apps and running against DVWA + PortSwigger web security labs to expose missed vulnerabilities, then fixing.

Also frames lightfuzz as BBOT's DAST module and closes the recon→DAST loop by feeding canonical baseline responses back into excavate so pages that only render after a real submission get mined for new params/URLs.

Form Extraction & DAST Loop

Excavate parameter extraction

  • WEB_PARAMETER dedup was silently discarding multiple values for the same param-name on one HTTP_RESPONSE (a results page with many <a href="?SortBy=<hex>"> links collapses to a single event because url_unparse strips the query string for GETPARAMs). The parameter extractor now groups yields by (type, name, url) and emits one WEB_PARAMETER per group with a new same_param_values field carrying the other observed values — surviving dedup so cross-value detectors can see them.
  • <select> picker: prefer the option carrying selected, else the first option's value (blank or not). Filter-style forms often use a blank default that matches all rows; substituting a specific choice could narrow results to nothing. Empty original_value is no longer coerced to None by the form-extractor's falsy check.

Lightfuzz → excavate baseline emission

  • Lightfuzz emits the canonical baseline response from each parameter's first compare_baseline() call as an HTTP_RESPONSE event, so excavate re-mines pages only rendered after a real submission (confirmation pages, redirect targets, forms behind a submit).
  • Per-WEB_PARAMETER HttpCompare cache keyed on the full prepared request signature: identical baselines across submodules (sqli + cmdi + path on a POSTPARAM with no envelopes were firing 3× the same baseline pair) collapse to one HttpCompare, one network pair, one emission. Cache cleared per-event at handle_event exit.
  • New HttpCompare.on_baseline_ready async callback fires once after baseline_1 is established.
  • New emit_http_response kwarg on compare_baseline() / baseline_probe() (default True); set False at sqli's FP-killer confirmation rounds.
  • New shared bbot/core/helpers/web/response_event.py builds HTTP_RESPONSE dicts from blasthttp 0.5.x Response objects — used by both http.py (DRY refactor) and lightfuzz.
  • New lightfuzz config: emit_baseline_responses (default true).

from-lightfuzz provenance tag

  • Lightfuzz-emitted HTTP_RESPONSEs carry a from-lightfuzz tag at emit time, distinguishing baseline-emission events from primary recon HTTP_RESPONSEs.
  • Added to the parent→child tag-propagation allow-list in event/base.py (parallel to affiliate / from-wayback), so URL_UNVERIFIEDs and WEB_PARAMETERs excavate emits off a lightfuzz baseline inherit the provenance.

assigned_cookies refresh

  • The cookies excavate originally captured may be stale by the time lightfuzz fuzzes (cycled tokens, expired sessions). The connectivity-test GET that already runs at the top of handle_event just hit the server; its Set-Cookie response is the freshest state we can get without an extra request. Fresh values are merged into event.data["assigned_cookies"] so subsequent baseline POSTs go out with the current session. Fresh wins on conflict; cookies the GET didn't re-issue (e.g. an auth cookie originally seen elsewhere) are preserved.

Keystream-Reuse (Many-Time-Pad) Detection — new

Many homebrew/legacy stream ciphers (e.g. ColdFusion CFMX_COMPAT) reuse one keystream across every encryption; XOR-ing any two such ciphertexts yields the XOR of their plaintexts. For natural-language / identifier plaintexts sharing a prefix this shows up as a leading run of zero bytes — mathematically impossible under correct encryption.

Per-value detectors (detect_ecb, padding-oracle, etc.) can't see this — it's only visible across multiple ciphertexts. New detect_keystream_reuse() in crypto.py:

  • Gathers candidates from event.original_value + additional_params + same_param_values
  • Pairwise-XORs decoded byte strings (hex/base64-shaped)
  • Severity ladder by zero-run length: ≥5 → HIGH/CONFIRMED; 3-4 or ≥95% ASCII-XOR-ASCII bytes → HIGH/PROBABLE; else MEDIUM/PROBABLE
  • Runs before the entropy gate so short ASCII identifier plaintexts (which produce low-entropy ciphertext) still get caught

Catches the canonical real-world fixture (4E4CDA8A93F87A XOR 4E4CDA8A93FF7B85… = 5-byte zero run) at HIGH/CONFIRMED.

Docs updated to frame lightfuzz as BBOT's DAST module.

False Negative Reductions & New Techniques

Serial (serial.py) — new

  • Python pickle static detection via benign base64/hex pickle payloads and unpicklingerror fingerprinting
  • Python pickle OOB RCE__reduce__ chains to socket.gethostbyname against a fresh interactsh subdomain. No external tooling; payload is built at scan time.
  • Java URLDNS OOB — hand-built Java serialization payload (pure stdlib, zero new deps). Fires on ANY Java deserialization sink since it only requires java.util.HashMap + java.net.URL (in every JVM since 1.1). Validated end-to-end against a real PortSwigger Apache Commons deserialization lab via oastify Collaborator callback.
  • skip_envelopes class flag — submodules whose payloads ARE the wire format (serial) opt out of envelope packing, so base64/hex/PHP-raw probes aren't re-encoded by the envelope system.

ESI (esi.py) — new

  • Remote-include OOB confirmation — sends <esi:include src="http://{interactsh}/"/> alongside the original tag-strip probe. Tag-strip proves ESI comment processing; remote-fetch proves exploitable processing with side-effects. CRITICAL / CONFIRMED.

Path traversal (path.py)

  • Simple (no a/ intermediate) variants for strict path resolvers that reject paths whose intermediate components don't exist:
    • Non-recursive-strip: ....//X, /....//X
    • Double URL-encoding: ..%252f{X}, %252f..%252f{X}
  • Fixes false negatives against PortSwigger's non-recursive-strip and superfluous-URL-decode labs.

CMDi (cmdi.py)

  • Arithmetic canary cascade: after a generic echo-canary match, try echo $((A*B)) (POSIX) then set /A A*B (cmd.exe). The product A*B never appears in the probe's literal bytes, so a response containing it proves real shell arithmetic expansion.
  • Matches upgrade confidence MEDIUM → HIGH and label the shell family (POSIX / cmd).
  • Echo-only matches (neither arithmetic confirmation fires) are demoted to a LOW "Possible Parameter Reflection" finding instead of CMDi — honest about what we observed, still surfaces for triage of adjacent reflection vulns.

XSS (xss.py)

  • HTML comment context — detects reflection inside <!-- ... {x} ... --> with a -->z breakout probe.
  • JS template literal (backtick) context — detects reflection inside `...${x}...` with a ${...} interpolation probe.
  • Quote-aware attribute context — tracks " vs ' as the wrapping quote rather than assuming double-quotes, so single-quoted-attribute XSS is no longer missed. Per-quote Tag Attribute, autoquote, and URL-scheme (javascript:) injection probes.

SSTI (ssti.py)

  • Added direct-form payloads: Jinja2/Twig {{1337*1337}}, Smarty {1337*1337}, Apache Velocity #set($x=A*B)$x (URL-encoded).
  • Baseline-gated — if the canary product already appears in the unaltered response, detection is suppressed to eliminate the coincidental-number FP.

Crypto (crypto.py)

  • Added SHA224 (28 bytes) to the hash-length identification table.
  • Removed dead flip branch from modify_string.

Core (lightfuzz.py, submodules/base.py)

  • NPE fix in interactsh_callback when a subdomain-tag details entry is partially populated.
  • New skip_envelopes class-level flag on BaseLightfuzz.
  • New register_interactsh_tag() helper on BaseLightfuzz — consolidates the tag → FQDN → subdomain_tags dict entry boilerplate previously duplicated across 5 call sites (ssrf, cmdi, esi, serial × 2).

False Positive Reductions — Structural

Event system (base.py)

  • Hoist url_extension from URL_UNVERIFIED up to DictEvent so WEB_PARAMETER events on .css, .js, .pdf URLs carry the extension attribute and get extension-* tags.

Module filtering (modules/base.py)

  • Extend the accept_url_special gate to apply to any event with a special URL extension (e.g. .js), not just URL-type events — lightfuzz no longer receives WEB_PARAMETER events for JavaScript URLs.

Lightfuzz (lightfuzz.py)

  • filter_event rejects WEB_PARAMETER events whose url_extension is in url_extension_static (pdf, doc, xml, etc.) — eliminates ~25 static-asset FPs from the reference scan.

SQLi confirmation loop (sqli.py)

  • Code-change detection now requires 3 consistent rounds (1 initial + 2 confirmations) with fresh baselines before emitting a finding.
  • Each confirmation round builds a new HttpCompare so the baseline is re-captured, ruling out stale-baseline FPs.
  • If the baseline status code changes between rounds, the finding is discarded as upstream flapping.

SQLi finding descriptions (base.py, sqli.py)

  • metadata() now surfaces additional_params for POSTPARAM/BODYJSON/HEADER/COOKIE findings (truncated).
  • Code-change SQLi findings include baseline/sq/dq request bodies for reproducibility.

False Positive Reductions — Prior

Serialization (serial.py) — centralize WAF detection via shared get_waf_strings(), skip Error Resolution when baseline status code is non-standard (>511).

XSS (xss.py) — verify XSS probe matches appear in the correct HTML context, not just anywhere in the response body.

SQLi (sqli.py) — suppress findings when a single-quote probe triggers a WAF 403 response.

Crypto (crypto.py) — endpoint stability pre-check for padding oracle and CBC bitflip; removed overly generic "access denied" error string.

Path Traversal (path.py) — centralized WAF detection list instead of hardcoded strings.

Excavate (excavate.py) — skip parameter extraction from out-of-scope redirect targets.

Global / Config

  • Added get_waf_strings() helper with 7 common WAF signatures.
  • Blacklisted CSRF tokens, ASP.NET session cookies, PKCE, and Akamai Bot Manager parameters.

Bug Fixes

  • Fix baseline_probe() using nonexistent eventtype key instead of type — was silently falling through to GET for all POSTPARAM/BODYJSON parameters.
  • Replace falsy-check with explicit None/empty-string check in incoming_probe_value().
  • paramminer_headers: use url_extension attribute instead of string endswith for static URL filtering.
  • Fix modify_string() treating position=0 as falsy.
  • Remove duplicate get_waf_strings() definition.

Crypto — new features (prior)

  • ECB mode detection via passive repeated-block analysis (zero HTTP requests).
  • CBC bit-flipping detection via active mutation of penultimate block positions (2 HTTP requests).

@liquidsec liquidsec marked this pull request as draft March 15, 2026 16:36
@github-actions

github-actions Bot commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Performance Benchmark Report

⚠️ No current benchmark data available

This might be because:

  • Benchmarks failed to run
  • No benchmark tests found
  • Dependencies missing

@codecov

codecov Bot commented Mar 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.89069% with 90 lines in your changes missing coverage. Please review.
✅ Project coverage is 91%. Comparing base (1256454) to head (75bf826).
⚠️ Report is 75 commits behind head on dev.

Files with missing lines Patch % Lines
bbot/modules/lightfuzz/submodules/crypto.py 82% 29 Missing ⚠️
bbot/modules/lightfuzz/submodules/sqli.py 80% 19 Missing ⚠️
bbot/modules/internal/excavate.py 91% 10 Missing ⚠️
bbot/modules/lightfuzz/lightfuzz.py 91% 10 Missing ⚠️
bbot/modules/lightfuzz/submodules/cmdi.py 90% 5 Missing ⚠️
bbot/modules/lightfuzz/submodules/base.py 95% 4 Missing ⚠️
bbot/modules/lightfuzz/submodules/serial.py 95% 4 Missing ⚠️
bbot/core/helpers/diff.py 67% 2 Missing ⚠️
bbot/modules/lightfuzz/submodules/path.py 80% 2 Missing ⚠️
bbot/modules/lightfuzz/submodules/ssti.py 78% 2 Missing ⚠️
... and 2 more
Additional details and impacted files
@@           Coverage Diff           @@
##             dev   #2967     +/-   ##
=======================================
+ Coverage     90%     91%     +1%     
=======================================
  Files        447     448      +1     
  Lines      40462   42504   +2042     
=======================================
+ Hits       36320   38256   +1936     
- Misses      4142    4248    +106     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@liquidsec liquidsec changed the title Lightfuzz improvements mar 26 Lightfuzz false positive reduction + crypto submodule improvements Mar 25, 2026
@liquidsec liquidsec marked this pull request as ready for review March 26, 2026 17:42
Comment thread bbot/core/helpers/misc.py
Comment thread bbot/modules/lightfuzz/submodules/serial.py Outdated
…dule

- detect_ecb(): passive repeated-block analysis, zero HTTP requests
- cbc_bitflip(): active test mutating penultimate block positions, 2 HTTP requests
- Fix modify_string() treating position=0 as falsy
… CSRF tokens, remove generic error string

- Add get_waf_strings() helper to misc.py with 7 common WAF signatures
- Use centralized WAF list in path.py and serial.py instead of hardcoded strings
- Blacklist CSRF tokens and ASP.NET session cookies in defaults.yml
- Remove overly generic "access denied" from crypto error strings
…erification, SQLi WAF detection

- Add endpoint stability pre-check to padding oracle and CBC bitflip tests
- Verify XSS probe matches appear in the correct HTML context
- Suppress SQLi findings when single-quote probe triggers WAF 403
- Blacklist PKCE and Akamai Bot Manager parameters
Prevents false positive deserialization findings against endpoints like
GlobalProtect that use non-standard status codes (e.g. 512).
URL events are now DictHostEvent so e.data is a dict, not a string.
@liquidsec liquidsec force-pushed the lightfuzz-improvements-mar-26 branch from 3c54eca to ab42ab3 Compare April 3, 2026 19:48
@liquidsec liquidsec changed the base branch from 3.0 to blasthttp-integration-clean April 3, 2026 19:48
liquidsec added 12 commits April 3, 2026 16:02
…rmation loop, better finding descriptions

- Hoist url_extension from URL_UNVERIFIED to DictEvent so WEB_PARAMETER events on static/special URLs get filtered
- Extend accept_url_special gate to all events with URLs, not just URL types
- Add lightfuzz filter_event rejection of WEB_PARAMETERs on url_extension_static URLs
- Add sqli code-change confirmation loop (3 rounds with fresh baselines) to suppress transient flaps
- Surface additional_params and probe request bodies in sqli finding descriptions
- Fix baseline_probe() "eventtype" -> "type" typo, fix falsy check in incoming_probe_value()
- Update paramminer_headers to use url_extension attribute
sqli: fix missing-comma bug that concatenated Oracle+MSSQL probes into
one malformed string. Add one-shot row-independent SLEEP payloads
(`' OR SLEEP(N) IS NOT NULL LIMIT 1-- -`) so blind sqli against
non-matching rows still delays predictably.

path: add simple `./X` / `../X` probes for stacks that walk paths via
the OS (Python open(), Go os.Open, Rust File::open) rather than
string-normalizing first. The existing `./a/../X` variants require the
intermediate `a/` to exist on disk; the new variants don't.

cmdi: three-stage cascade per delimiter — generic echo canary, then
POSIX arith `echo \$((A*B))`, then Windows `set /A A*B`. When an arith
probe confirms execution, upgrade to HIGH confidence. When only the
generic probe fires (no shell confirmation), emit a separate
"Possible Parameter Reflection" finding rather than overclaiming cmdi.
Preserves the adjacent-vuln reflection signal without mislabeling it.

tests: add save-point tests for each new detection path, and bring
the rand_string mocks into compliance with numeric_only=True so the
new arith code doesn't crash on mocked values.
Previously `determine_context` recognized only three contexts: between
tags, double-quoted attribute, and plain JS string. Real-world XSS
reflections into single-quoted attributes, non-`action` URL attributes
(href, src, formaction, etc.), HTML comments, and JS template literals
were all missed.

Added:
- Single-quoted attribute detection + quote-aware breakout probes.
  Tracking the wrapping quote type prevents false positives from the
  wrong-quote char reflecting harmlessly inside.
- URL-scheme probe generalized from `action="javascript:..."` to match
  any URL-bearing attribute.
- HTML comment context: detected via `<!-- ... {refl} ... -->` and
  probed by testing whether `-->` survives reflection.
- JS template literal (backtick) context: detected via backtick-wrapped
  reflection inside <script>, probed via `${...}` interpolation survival.

Also fixed an early `return` in the existing in-javascript path that
aborted fuzz() entirely when the quote context was "outside" — now it
just skips the escape-the-escape probe and falls through to the new
template-literal check.

Tests: 4 new save-point classes covering single-quote, URL-scheme-href,
HTML comment, and template literal. Existing xss test assertions updated
for the new `Tag Attribute ({q} quoted)` / `URL-scheme Injection` context
format.
Two changes to the ssti submodule:

1. Add a direct `{{1337*1337}}` probe and a single-brace Smarty variant
   `{1337*1337}`. The existing `1,787{{z}},569` trick relies on
   `{{z}}` silently rendering as empty, which fails in StrictUndefined
   Jinja2 environments and other engines that raise on undefined vars.
   A direct arithmetic probe still works in those contexts.

2. Add a baseline-response check: before running any probes, fetch
   the response with the original value and confirm the detection
   canaries (1787569 / 1,787,569) aren't already present. If they
   are, abort — the number is part of the page, not a template-eval
   result. This eliminates the rare coincidental-number false positive.

Tests: 2 new save-points. One exercises the direct {{A*B}} path with
a mock template engine that raises on {{z}} (simulating StrictUndefined).
The other confirms the baseline check suppresses findings on pages
that already contain 1,787,569 in static content.
Add Apache Velocity `#set($x=A*B)$x` probe (URL-encoded since `#`
would otherwise be interpreted as a URL fragment and truncate the
payload). Save-point test with a mock that only recognizes the
Velocity syntax, proving the new probe fires on engines the existing
probes miss.
Two changes:

1. Python pickle coverage for the serial submodule.
   - Static payloads: benign `pickle.dumps('test')` in both base64 and
     hex dicts, plus `unpicklingerror` added to the error-string list
     for the Differential Error Analysis path.
   - Blind RCE: generates a fresh pickle payload per scan whose
     `__reduce__` calls `socket.gethostbyname(interactsh_subdomain)`.
     Because pickle is Python-native, the payload can be rebuilt per
     scan with a unique OOB callback — no pre-generated template or
     out-of-process gadget tooling required.
   - `uses_interactsh = True` so lightfuzz spins up interactsh when
     serial is the only enabled submodule.

2. `skip_envelopes` opt-in on BaseLightfuzz.
   - When a submodule's payloads ARE the wire format (base64-encoded
     serialized objects, hex blobs, etc.), the envelope system must
     not re-pack them based on what the parameter's original value
     looked like. Without this, a base64-wrapped plain-text parameter
     would cause serial's already-base64 pickle payload to be
     double-encoded before transit.
   - Class-level flag: submodules set `skip_envelopes = True` and the
     base-class helpers `incoming_probe_value` / `outgoing_probe_value`
     skip unwrap/pack accordingly. Default stays False for every other
     submodule.

Tests: 2 new serial save-points (pickle Error Resolution + pickle OOB
interactsh). The OOB test mocks interactsh, decodes the incoming
payload, extracts the embedded subdomain, and triggers a mock
interaction to confirm the detection path fires. Full lightfuzz suite
75/75 (was 73).
liquidsec added 7 commits May 11, 2026 15:14
baseline_probe was firing a bare request with no body — for POST baselines
the server received an empty body and rendered a default/error view, not
the actual form submission. The resulting HTTP_RESPONSE was useless for
excavate to re-mine, defeating the recon→DAST loop on real targets.

Now uses prepare_request() (the same builder compare_baseline uses) so the
baseline request carries the parameter's original_value plus every sibling
field from additional_params. GET baselines get them in the querystring;
POST/BODYJSON in the body; HEADER/COOKIE on the wire.

Regression test: Test_Lightfuzz_baseline_probe_form_submission stands up a
server that only reveals /crypto-baseline-secret when the POST body
includes the form's token field. Verified the test catches the prior bug
(reverted the fix in-stash, the assertion fails on the missing
URL_UNVERIFIED for /crypto-baseline-secret).

Also fixes Test_Lightfuzz_crypto_error fixture: the server returned the
crypto error for any non-empty secret value, including the canonical one.
With baseline_probe correctly sending the canonical value, the baseline
itself contained the error and error_string_search had nothing to diff
against. The fixture now mirrors a real crypto-vulnerable backend
(canonical value decrypts cleanly; any other value errors).
…y slice

YARA's regex engine has a hardcoded ceiling (~4 KB) on `.*` quantifiers
between anchored points, so the form-discovery rules of shape
`<form ...>.*</form>/s` silently failed to match any form whose body
exceeded that bound — a single <select> over a real-world dataset
(hundreds of options) is enough. Downstream consumers (lightfuzz,
crypto's cross-value detectors) never saw WEB_PARAMETER events for
those forms.

Fix:
- Narrow each form-rule discovery_regex to match only the opening
  <form ...> tag (GetForm, GetForm2, PostForm, PostForm2,
  PostForm_NoAction, _GenericForm).
- Override ParameterExtractor.preprocess to retain per-instance YARA
  match offsets (the base class flattens to a set, losing them).
- ParameterExtractorRule.__init__ now accepts the response body and the
  match offset; form_body_slice() returns a bounded slice of the body
  starting at the YARA offset.
- The existing Python extraction_regex (non-greedy `<form...>...</form>`
  with no cliff) runs on that slice and finds the form normally.
- New excavate config: max_form_bytes (default 256 KB) caps worst-case
  per-form extractor work. Forms whose body exceeds the cap are
  skipped — defensive cap, not best-effort partial extraction.

Tests:
- TestExcavateGiantForm: 5000-option <select> (~210 KB form), asserts
  all fields are extracted. Verified to fail (no fields emitted) before
  this fix.
- TestExcavateGiantFormExceedsMaxBytes: ~240 KB form with
  max_form_bytes=32 KB, asserts no hang/OOM and no form fields leak
  through under the bound.
- TestExcavateFormAttributeOrder: covers both
  `<form action=X method=post>` and `<form method=post action=X>`.
…ection

New Stage 0 in the crypto section covering the cross-value pairwise-XOR
check, its severity/confidence ladder, and why it bypasses the entropy
gate.
…ization

Excavate stores original_value=None for <input> elements without a value=
attribute (distinct from value="" in event metadata). Browsers submit
both cases identically as name= (empty value), but Python's urlencode
renders None as the literal text 'None' — the target server then sees
name=None and typically rejects the request as malformed.

Coerce None → '' at the prepare_request wire boundary so the request
matches browser-equivalent semantics. Affects the populate_empty=False
path (baseline_probe and any explicit caller); the populate_empty=True
path already replaces None with random strings in additional_params_process.

Regression test Test_Lightfuzz_none_in_additional_params: server reveals
the secret only when the no-value-attr field arrives as '' (empty)
rather than the literal 'None'. Verified the test fails on the pre-fix
codebase and passes after.
…'a')

baseline_probe now fires up to two requests per WEB_PARAMETER:

* Probe A — form as captured (existing behavior). Filter-style forms
  whose 'show all results' state is the no-narrowing default produce
  content here.
* Probe B — substitute 'a' for the fuzzed field's value, only when its
  original_value is None or ''. Catches search-style forms whose useful
  content appears only when the primary text input is non-empty.

Both responses go through emit_baseline_response so excavate can mine
whichever returned populated content. No classifier picks between them —
they both fire and the response is the arbiter.

The baseline submission is hoisted to lightfuzz.handle_event so it runs
once per WEB_PARAMETER regardless of which submodules are enabled (it's
a module-level recon→DAST loop feature, not a crypto-specific one).
Crypto still aborts early on empty probe_value as before — that's the
correct behavior for crypto's per-value analyses, separate concern.

Per-signature LRU cache (_baseline_probe_response_cache, bounded at 200
entries) ensures identical baselines across siblings of the same form
collapse to one network request and one emission.

Tests cover the two functional cases:
* Search-style form with empty input → Probe B reveals secret link.
* <select> with a selected option → Probe B skipped (real default
  preserved), no extra POST.
…kill PostForm_NoAction phantom

- excavate stamps host_url on form-derived WEB_PARAMETERs when the form's
  action URL differs from the discovery page; lightfuzz primes its
  connectivity GET against host_url and stamps Referer on probe requests
- lightfuzz coalesces concurrent connectivity_test GETs per prime_url via
  NamedLock so the 4-5 WEB_PARAMETERs excavate emits per page share one
  GET instead of racing parallel ones under _module_threads=4
- lightfuzz dedup hash includes body_md5 for non-FINDING events so dual
  baseline_probe (Probe A vs Probe B) surfaces distinct bodies to excavate
- regexes: tighten post_form_regex_noaction with a negative-lookahead so
  it stops over-matching forms with action — was emitting a phantom
  WEB_PARAMETER whose url fell back to event.url
- test: rewrite cookie_refresh server to bootstrap-then-validate (matches
  real CSRF protections); add host_url priming end-to-end test
@ausmaster ausmaster added this to the BBOT 3.0 - blazed_elijah milestone May 20, 2026
@liquidsec liquidsec changed the base branch from blasthttp-integration-clean to dev May 21, 2026 02:26
…-mar-26

# Conflicts:
#	bbot/modules/lightfuzz/submodules/serial.py
Comment thread bbot/test/test_step_2/module_tests/test_module_excavate.py Dismissed
Comment thread bbot/test/test_step_2/module_tests/test_module_excavate.py Dismissed
liquidsec added 7 commits May 26, 2026 01:48
Both build_query_string and send_probe_with_canary appended ?name=value
to event.url with an f-string, producing URLs with multiple ? segments
when the source WEB_PARAMETER already carried a querystring (common from
wayback when url_querystring_remove=False). Route through add_get_params
so the probe merges into the existing querystring instead.
…cess

ParameterExtractor.process read response_body from event.data['body'],
which body-spill pops at HTTP_RESPONSE construction. Form-field
extraction silently emitted no WEB_PARAMETERs whenever spill was on
(default). Switch to event.body so the spill store is consulted.
sqli: two-stage scaling-delay confirmation for time-based blind detection
…ity cache poisoning

- crypto: filter structured hex IDs (Mongo ObjectIds, hex timestamps,
  sequential counters) from keystream-reuse detector via tail analysis
- cmdi: reject leading-zero arithmetic multiplicands (bash octal divergence)
- lightfuzz: restore event type/flags after try_get_as_post conversion passes
- lightfuzz: don't cache None connectivity responses (transient failure)
- Add 16 regression tests covering all four fixes
@ausmaster ausmaster self-requested a review June 9, 2026 16:52
@liquidsec liquidsec merged commit f66c0db into dev Jun 9, 2026
20 checks passed
@liquidsec liquidsec deleted the lightfuzz-improvements-mar-26 branch June 9, 2026 17:03
@liquidsec liquidsec mentioned this pull request Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants