DEV Community

Rocky
Rocky

Posted on

Your Brute-Force Alert Has a Blind Spot Called Password Spraying

Every SOC has some version of the same brute-force rule: an account racks up N failed logins in M minutes, an alert fires, an analyst looks at it. It works fine against the noisy version of the attack, a script hammering one username with a password list until it locks the account or gets lucky. It works fine right up until the technique on the other side changes shape, and then the rule doesn't just miss the odd event, it stops seeing the attack category entirely.

Password spraying inverts the attack instead of intensifying it. Instead of many passwords against one account, it's one or two passwords, a season-and-year, "Password1!", whatever shows up near the top of a breach-pattern list, tried against hundreds or thousands of accounts, spread out over hours so no single account ever crosses the per-account failure threshold. Ten failed logins in five minutes never happens, because no account gets more than one or two attempts in that window. The rule doesn't fire not because the attack was subtle, but because the rule was built to look for a completely different shape of attack: loud, narrow, and fast.

The failure mode here is worse than "we missed one alert." It's "we don't find out until something downstream breaks," because a spray attack only needs one weak password out of thousands of accounts to land, and that one successful login looks, to a per-account rule, exactly like any other normal auth event. Nothing about the success itself is anomalous. The anomaly was sitting in the pattern of attempts that came before it, spread thin across accounts the rule was never grouping together in the first place.

The fix is changing what you group by, not lowering the threshold. Instead of counting failures per username, count distinct usernames attempted per source IP, or per ASN if the spray is distributed across a botnet, within a time window. More than fifteen or twenty distinct accounts hit from one source in an hour, each with only a handful of attempts, is the shape to alert on. That single change, from "failures per account" to "distinct accounts per source," is the difference between a rule that catches loud brute-forcing and one that catches both.

Writing that logic once isn't actually the hard part. The hard part is that most shops don't run one SIEM. Splunk for one team, Sentinel because the org is on Azure, Elastic because someone stood it up three years ago and it never got replaced, and the detection logic has to exist as a working, correct rule in whichever query language that specific environment speaks. count(distinct username) by src_ip reasons the same everywhere. Getting it into working SPL, then KQL, then EQL, then a portable Sigma rule that translates cleanly into all three, is where the actual engineering time goes, and it's also where the same rule quietly drifts out of sync across tools if nobody's tracking it as one source of truth.

That exact discipline, reasoning a detection out once and shipping it correctly across Sigma, Splunk SPL, Sentinel KQL and Elastic EQL instead of rebuilding it from scratch in each tool, plus a chapter's worth of near-miss detection gaps like this one, is what the Detection Engineering book for SOC analysts is built around: https://resources.codelivly.com/product/soc-analyst-level-3/

Top comments (0)