HackBlue

Tools · Web testing tools · 17 / 23

What not to trust in a scanner alert

A ZAP alert is a rule’s claim about a response. Trust the raw History entry more than the risk label, and trust a header you can see more than a plugin name. You confirm or you reject. You do not “follow the alert” into a deeper attack.

17 min read · practice only with authorization

An alert is a claim with a default costume

ZAP’s alert list is a set of claims produced by rules. Each claim has a costume that looks like a decision: a color, a risk word, a confidence word, a CWE number, a paragraph of advice. Some of that costume is useful metadata. None of it is a substitute for the response. The response is in History, and it is usually short on this lab. Read it.

The habit is the same one the Burp lessons used on a single invoice. Method, path, status, the field or the header named in the claim, and a sentence. The scanner changes the pace, because it can hand you fifty claims. It does not change the standard of confirmation. Fifty unread claims are a queue, not a result.

A true word and a false claim

Harbor Ledger’s invoice JSON includes an error field set to null so the client can render a form. A passive rule that looks for the word error in a body will fire. The word is there. The disclosure is not. Status 200, a normal invoice, a null field. Mark the alert false for that URL, and write the reason in the note so the next baseline does not convince you all over again. If the tool has a false-positive control, use it. If it does not, the note is enough for this lab.

The opposite mistake is dismissing a boring alert that is accurate. A missing content security policy will not look like a scene from a movie. It is still a true statement about the bytes. On a loopback lab the people affected are you and anyone who copies this app’s headers onto a real origin later. That is a good reason to fix it here, where a reload proves the fix, and a poor reason to inflate the story.

Low confidence is a request to look, not a request to escalate

When confidence is low, the rule is telling you it guessed. A common case in a baseline is a plugin that fetched a URL the app does not have, received the lab’s plain 404, and filed a dramatic name. Open the 404. If the body is the lab’s ordinary not-found page, the claim is not confirmed. Record the URL, the status, and “not confirmed,” and move on.

What you do not do is promote the guess. These lessons do not turn a plugin name into a list of inputs to try, and they do not turn the active scanner on to raise the confidence number. Confidence goes up because you looked at better evidence, or it stays low because the evidence is not there. The active scanner is a separate decision with its own written scope, on a lab you can restore, and it is outside this page on purpose.

Snippet
Rule: (whatever the plugin called it)
URL: http://127.0.0.1:3000/api/invoices/1042
Confidence: Low
History status: 200
History agrees: no
Why: body is a normal invoice. "error": null is a field.
Change: none for this row
Will not do: start an active scan from this row

Worked case: sort eight alerts into two piles

Take up to eight alerts from the baseline report, all on http://127.0.0.1:3000. For each, fill the confirmation note: agree or not, from History, in one sentence. You will end with two piles. The agree pile on this app is mostly missing headers and similar hardening, and the change is a header you can add and re-request. The disagree pile is words in JSON, generic 404s, and anything whose evidence you cannot find.

The outcome of the exercise is the two piles and one header you actually added, confirmed by a single new History row. It is not a smaller number in the badge. If you want the badge to drop, re-run the baseline after the header exists and expect the missing-header rows to go away and the false ones to return until you suppress them. That second run is optional. The confirmation note is the work.

Diagram

A claim has to survive the response

  1. Claim

    The alert names a rule, a URL, a default risk, and a snippet of evidence.

  2. Response

    History shows the status, the headers, and the body that were actually returned.

  3. Decide

    The claim matches the bytes, or it does not. Confidence tells you how hard the rule was guessing.

  4. Stop

    A confirmed item becomes a change in the lab app. An unconfirmed item becomes a note that says unconfirmed. Neither becomes a second, harsher scan.

Three alerts, three different amounts of trust

  • Missing header

    High confidence, and you can see the header is absent. Trust the observation. The risk number is still just the rule’s default.

  • Error word

    The body contains the word error inside "error": null on a healthy JSON response. The rule saw the word. The app is reporting an empty error field. Do not confirm it.

  • A guessed injection

    Low confidence, and the evidence is a generic error page the spider provoked by asking for a strange path. Read it. Do not treat the plugin name as proof, and do not start an active scan to “make sure.”

Confirmation note
Rule:
URL:
Confidence:
History status:
History agrees: yes / no
Why:
Impact in this lab, in one sentence:
Change, if you own the bug:
Will not do: start an active scan from this row

Check yourself

1. GET /api/invoices/1042 returns 200 and {"error": null, "id": "1042", ...}. An alert says the application disclosed an error. What do you trust?

2. A low-confidence alert is named for an injection flaw. The evidence is that the spider received the lab’s ordinary 404 page. What is the next step in this lesson?

3. You confirmed that HTML responses omit Content-Security-Policy. Which sentence is yours, as distinct from the tool’s default risk?