Skip to content

fix: bound quantifiers without rewriting character classes - #3068

Open
ashvinctrl wants to merge 1 commit into
sherlock-project:masterfrom
ashvinctrl:fix/quantifier-bound-character-class
Open

ashvinctrl wants to merge 1 commit into
sherlock-project:masterfrom
ashvinctrl:fix/quantifier-bound-character-class

Conversation

@ashvinctrl

Copy link
Copy Markdown

set_pattern_upper_bound rewrites every unescaped + and * in a regexCheck before the false positive fuzzer generates a handle. Inside a character class those are ordinary members, not quantifiers, so the substitution changes which characters the class accepts.

Two targets in the manifest are affected today:

Wordnik          ^[a-zA-Z0-9_.+-]{1,40}$   ->  ^[a-zA-Z0-9_.{1,15}-]{1,40}$
CyberDefenders   ^[^\/:*?"<>|@]{3,50}$     ->  ^[^\/:{0,15}?"<>|@]{3,50}$

Wordnik loses + and gains {, ,, }. CyberDefenders stops excluding * and starts excluding digits. Sampling 20k handles from each rewritten pattern, 54% and 26% of them fail the site's real regexCheck, so sherlock returns Illegal and the check reports a false positive against a healthy target:

AssertionError: Wordnik produced false positive with pattern ^[a-zA-Z0-9_.{1,15}-]{1,40}$, result was Illegal

Running -m validate_targets_fp --chunked-sites "Wordnik,CyberDefenders" 15 times: 7 of 15 runs fail on master, 0 of 15 after this change. That job gates every data.json PR and also runs nightly over the full manifest in the exclusions updater, so the noise is recurring rather than one-off.

The fix walks the pattern and applies bounds only where a quantifier can appear. Escaped literals are skipped, which also fixes \+, where the + is a real quantifier that the old lookbehind declined to bound. A lower bound above the cap now lifts the bound for that one quantifier instead of leaking into the rest of the pattern, which is what the nonlocal in the previous version did.

Behaviour outside character classes is unchanged: +, * and {n,} still collapse to the same bounds, and generated handles are still capped at 15 characters.

Tests cover the quantifier cases, the escaping rules, both manifest patterns above, and a sweep asserting that every regexCheck in the manifest still accepts what its bounded form generates. Six of them fail against the current implementation. Patterns using lookarounds are skipped in the sweep, since rstr cannot honour those and they fail independently of bounding.

set_pattern_upper_bound substituted every unescaped + and * in a regexCheck,
including those inside a character class where they are ordinary members. The
rewritten class accepts a different set of characters, so the fuzzer generated
usernames the target's own regexCheck rejects and the false positive check
reported Illegal against a healthy target.

Walk the pattern instead of running blind substitutions, so bounds are applied
only to quantifiers. Escaped literals are skipped, and a lower bound above the
cap now raises the bound for that quantifier alone rather than for the rest of
the pattern.
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.

1 participant