CAPABILITY
A finding is something the target had to do.
Most scanners tell you what might be true. This one makes the application prove it. If nothing had to be computed, fetched or decided, it is not a finding. It is a guess with a severity label stapled to it.
- 15/15 Detection benchmark
- 99/99 Template pack
- 9/9 Injection oracle
- 48/48 The full lab
- 22 Classes covered
199933 * 314573 an expression, never its answer199933 * 314573 not a finding62893523609 a findingCoverage
22 classes, and what proves each one.
Click any class to see what the application has to actually do before we will write it down.
Injection8
Every one of these is confirmed by something the application had to compute or fetch, never by the payload coming back.
Out-of-band3
Classes where the response tells you nothing, so the target has to contact something instead.
Serialization and parsing2
The class most scanners can only find in Java, because Java is the one language where somebody already wrote the universal gadget chain for them.
Business logic4
The classes no payload list will ever reach, because nothing is malformed. The request is perfectly valid. The application is just wrong.
Access and exposure5
What a caller can reach that belongs to somebody else, or should not be reachable at all.
Standards
What it covers, and what it will not claim.
Pick a list. Every row that says yes names the class that does the work, and every row that says no says why, because a coverage matrix with no gaps in it is a brochure.
8 covered 1 partly 1 not claimed The list this engine was built against. Eight fully, one partly, and one that nothing on the outside can honestly report.
MITRE ATT&CK is deliberately not here. It describes what an adversary does across a whole intrusion, and almost all of a web scanner collapses into one technique in it. A six-row ATT&CK matrix would look impressive and tell you nothing, which is the opposite of what this section is for.
The unusual part
Four things most scanners will not do.
Deserialization outside Java
Make the parser name itself on bytes no format can read, then go quiet on valid input. Both halves, or nothing.
Chains, with the coincidence removed
An SSRF that fetched the Redis another engine found. One probe showed both halves, or it is not reported.
What a file read is worth
/etc/passwdproves traversal and nothing else. A confirmed read goes looking for the config beside it. Key names recorded, never values.Bugs with nothing malformed in them
A checkout that skips the charge. A coupon redeemed eight times at once. No payload list finds these.
Behind the login
A scanner that stops at the login page is testing a different application.
An unauthenticated scan is not a lighter version of a real test. It is a thorough test of a much smaller application that happens to share a domain with yours.
| Login type | Notes | |
|---|---|---|
| Bearer tokens | Static | A header you already hold. |
| Custom headers | Static | Whatever your API actually reads. |
| Cookies | Static | A session captured from a browser. |
| HTTP basic | Static | Still everywhere, still worth testing behind. |
| Form login | Interactive | Submit a username and password, keep the session that comes back. |
| OAuth without a browser | Interactive | Client-credentials and password grants, run headless. |
| OAuth, SSO and MFA | Interactive | Authorization-code flows that need a real browser driven through them. This is the one most tools give up on. |
Where the password goes
- Youhand over a login, once
- Vaultencrypted at rest, resolved by reference
- Brokersigns in, returns a short-lived token
- Nodeonly ever sees the token
That broker used to open your login page from our address, breaking a rule we had written down ourselves. Found in an audit, fixed, and written up in whose IP address is your scanner using?
Broken authorization
The class where nothing in the request is wrong.
One user reading another user's data. First on the OWASP API list, and the class automated tools are worst at, because a perfectly valid request from the wrong person is the vulnerability.
Three names for one mistake
| Name | Level | What happens |
|---|---|---|
| BOLA | object | You ask for order 1043. It is somebody else's. You get it. |
| BFLA | function | A normal account calls an admin action and the server does it. |
| BOPLA | property | You send role or verified and it sticks. |
Replay as everyone, then compare
You supply the logins. Each endpoint is sent as every identity.
| Signed in as | What it catches |
|---|---|
| admin | The privileged baseline. What a legitimate high-privilege caller can reach. |
| user A | An ordinary account that owns some objects. |
| user B | A second ordinary account, used to reach for user A's objects. This is the one that finds BOLA. |
| anonymous | No credentials at all, for the endpoints that forgot to ask. |
200 user A's order allowed, and correct200 user A's order the owner200 user A's order nobody checked who asked401 nothing correctly refusedAn admin reading a user's object is the job, not a bug, so direction across the boundary counts. Every finding is re-issued and has to reproduce before it reaches you.
We point this at ourselves too, which is how we found an assistant that could read other people's data.
Correctness
Every false positive we shipped had one cause.
The scanner accepted something it had caused as evidence. Four classes, four bits of code, one mistake made four times. It is a stupid mistake and it is very easy to make.
- Remote code executiona page echoing our own user agent
- Seven file readsa docs page full of PHP examples
- SSRF, confirmedour own redirect-following client
- Nine blind SQL injectionsour worker changing the admin password mid-scan
9 rules came out of it, enforced in code rather than in review:
They cost us real findings. That trade, and the one rule that turned out to be wrong, is in every false positive we have shipped had the same cause.
Evidence
Numbers, and what produced each one.
These come from our own harnesses, so treat them as our homework rather than an audit. One of them is published in full, with every miss sorted by cause and every false positive named and counted against us, and that is the only one you can mark yourself.
15/15
detected, no false positives
Detection benchmark
Includes targets built to fool a scanner, one of which serves the words "Index of /" as ordinary prose.
99/99
template fixtures fired, none cross-fired
Template pack
Every template in the pack now has a target that must make it fire, plus one it must stay silent on. The single exception is Log4Shell, which only ever fires on an out-of-band callback, so it is proved by the OAST harness instead and says so by name.
9/9
found, nothing missed, nothing invented
Injection oracle
Nineteen endpoints, ten of them safe by construction.
48/48
answer-key entries, keys written first
The full lab
Nineteen deliberately vulnerable targets with answer keys written before the scan, plus one whose correct score is silence. This is one lab and it is finite; the number to watch is what happens when entries are added to it, not this one.
Read the report124
adversarial cases, no crashes
Robustness
Five never finish, against servers that never end a response. They hit the engine deadline. Stated because a scorecard without its remainder is not one.
The lab run is the one worth reading, because it is the only one here that is capable of reporting a bad result, and it has.
Templates
100 of them, and you can read every one.
A detection rule you cannot read is one you are trusting on vibes. These are Apache-2.0 and editable, in the Nuclei format, so packs you already have will mostly just work.
One of them, whole
Twenty-two lines, copied out of the pack rather than written for this page. Note the and: a 200 on its own proves nothing, because plenty of servers answer 200 for
a path that does not exist, so the body has to look like a real git HEAD file too. That is
this whole page in one rule.
id: git-head-exposure
info:
name: Exposed .git/HEAD
severity: medium
description: A readable .git/HEAD confirms the .git directory is
web-served, which typically allows reconstructing the full source
tree (and any secrets committed to it).
tags: exposure,git,source-code
metadata:
category: exposures
http:
- method: GET
path:
- "{{BaseURL}}/.git/HEAD"
matchers-condition: and
matchers:
- type: regex
part: body
regex:
- "^ref:\s+refs/heads/"
- type: status
status:
- 200 Nuclei and cortex, on the same file
These are not rival formats and it would be dishonest to draw them that way. The template above runs in both. What differs is what each engine does with it.
Read by both
- id, info, severity, description and tags
- http requests, or the older requests spelling
- method, path, headers, body and payload lists
- matchers: word, regex, status, size and dsl
- matchers-condition, per-matcher condition and negative
- unsafe, for probes that need the raw request target
Nuclei
A bigger tool at the format level, and we are not going to pretend otherwise.
- Protocols beyond HTTP: dns, network, ssl, file, code, javascript and headless
- Workflows, for chaining templates conditionally
- Extractors, for pulling values out of one response and into the next
- Variables and helper functions across a much larger DSL
- A public template repository with thousands of community rules
Cortex
Reads the subset above and adds two rules the format itself has no opinion about. Both are tests, so a template that breaks one fails the build rather than reaching a customer.
- A template may not match on a word it transmits, unless it is tagged reflection and says so in writing
- Every template must declare its class in its tags, so no finding falls through to a generic bucket
template `{id}` matches on `{word}`, which it sends itself - an echo
would satisfy it. Use a marker the target can only produce by executing
something, or tag the template `reflection`.Templates are the commodity half, open on purpose. The oracles above are not templates at all, which is why a template count is a poor way to compare two scanners and everybody keeps doing it anyway.
Limits
Where it is weak.
- 5 of 124 robustness cases never finish
- Servers that never end a response hit our own deadline. No crashes, no memory blowups.
- Race conditions and request smuggling
- Cannot be tested without changing the target, so they are opt-in and capped.
- Flow-order abuse
- Needs a recording of the real flow, not a crawl.
- Forms that change a credential
- Never submitted, so some vulnerable endpoints get reported as present and then left completely alone.
- One template is not proved here
- Log4Shell only ever fires on an out-of-band callback and declares no response matcher at all, so a fixture that answers the request would prove nothing. It is proved by the OAST harness instead, and named in the scorecard rather than quietly counted.
It sends real payloads, and only ever at hosts inside the scan target. Run it against things you own or have written permission to test.
Questions