skip to content

What does ZAP's AbstractPlugin.sendAndReceive change about an active rule's request before sending it?

level: middleimportance: should knowfreq 38%

answer

  1. one door for every attack request
  2. the clone is sent, not the original
  3. conditional headers are stripped first
  4. pluginHeader names the sending rule

basics

~20 s

It clears If-Modified-Since and If-None-Match so a 304 cannot hide the response, recomputes Content-Length for the body the rule just edited, optionally adds a scan-id header, regenerates any anti-CSRF token, and follows redirects through a scope check.

solid answer

~50 s

`AbstractPlugin.sendAndReceive` is the single door every active rule sends through, and it is not a thin wrapper. Before the request leaves it removes `If-Modified-Since` and `If-None-Match`, so the target cannot answer a conditional `304` and hide the body the rule is judging; it recomputes `Content-Length` for the body the rule has just rewritten, and removes it entirely when the body is empty and the method is one that does not normally carry one. If `scanner.pluginHeader` is enabled it stamps the rule's id into a ZAP scan-id request header, which is how you attribute attack traffic in the target's own access log. If anti-CSRF handling is on it regenerates the token first — which can itself cost an extra request. Redirects are followed by default, through a validator that keeps the hops inside the scan's scope, and afterwards the engine is told about the message so it counts against that rule.

go deeper

for a junior

Know that active rules do not send raw sockets of their own: they hand a message to one inherited method, and that method tidies the request before it goes. The rule works on a clone, never on the recorded original.

for a middle

Be able to list what the send path changes and why: conditional headers cleared so a not-modified reply cannot hide the body, length recomputed for the rewritten body, optional rule-id header, anti-CSRF refresh, scope-checked redirects.

for a senior

Use the rule-id header when you have to defend a scan to whoever owns the target, and remember that anti-CSRF refresh inflates message counts. Both turn arguments about the scan into evidence about it.

for a principal

Decide as a matter of policy whether attack traffic from your pipelines must be self-identifying. Tagging it costs one setting and makes every downstream conversation about load, errors and audit far shorter.

## One door, and it is not thin Every active scan rule in ZAP inherits from core's `AbstractPlugin`, and every attack request a rule makes goes out through that class's `sendAndReceive`. It is the seam where a rule's crafted message stops being a data structure and becomes traffic. Knowing what the seam does to the message is what lets you read the target's logs and the scan's own counters and have them agree. A rule never mutates the recorded message it was given. It calls `getNewMsg()`, which returns a clone of the base request with an empty response, edits that clone, and sends it. The original stays intact as the reference the rule compares against. ## What happens on the way out | step | what it does | why it matters to you | |---|---|---| | clears `If-Modified-Since` and `If-None-Match` | removes cache-validation headers inherited from the recorded request | otherwise the target may answer with a not-modified status and no body, and the rule would be judging an empty response | | recomputes `Content-Length` | sets it to the length of the body the rule just rewrote, or removes it when the body is empty and the method does not normally carry one | a stale length either truncates the payload or hangs the connection | | optional scan-id header | when `scanner.pluginHeader` is on, stamps the sending rule's id into a ZAP scan-id request header | the only reliable way to attribute a line in the target's access log to a specific rule | | anti-CSRF regeneration | when enabled, refreshes the token in the request first | the refresh can itself issue an extra request, so a rule's message count is not always its payload count | | listener hooks | scripts and other senders registered on the HTTP sender may still rewrite the request | a header you set elsewhere can be changed after the rule is finished with the message | | redirect following | on by default, through a redirection validator tied to the scan's scope | a redirect will not silently walk the scan onto a host you did not authorise | | notify the engine | hands the sent message back to `HostProcess` | this is what increments that rule's message count in the scan's own statistics | ## The scan-id header is the one to remember `scanner.pluginHeader` is **off by default**. Turn it on and every active-scan request carries a header naming the rule that sent it. Three things become possible that are otherwise guesswork: - you can prove to whoever owns the target which of your requests came from the scan; - you can attribute a five-hundred error or a poisoned record to a specific rule instead of to "the scanner"; - a target-side filter can allow, rate-limit or tag that traffic distinctly. The constant holding the name is spelled in lower case, and HTTP header names are case-insensitive anyway, so search the target's logs case-insensitively rather than for one exact spelling. This is a trap the project has elsewhere too: a header constant's literal casing in the source is not a statement about what the receiving end must match. ## Two counters that are not the same number A rule's message count, which the engine logs when the rule completes, counts every message that came back through `sendAndReceive`. That includes: 1. the payload requests the rule meant to send; 2. any extra request the anti-CSRF token refresh made on its behalf; 3. nothing from the engine's own baseline probing, which does not pass through this method and is attributed to no rule. So "this rule sent N messages" is a fact about the send path, not a count of attack payloads. If you are budgeting request volume against a fragile test environment, count the shape of the rule (per host, per node, per parameter) rather than trying to reverse a payload count out of the log. ## What the passive side does not have The reason `sendAndReceive` lives on `AbstractPlugin` and nowhere else is that it marks the boundary of the whole subsystem. The base class a passive rule extends has no send method at all, and its alert builder refuses to accept an attack string — the boundary is enforced by the type system rather than by convention. When you are asked what makes a rule "active", the honest answer is not what it looks for; it is that it inherits a method that puts bytes on the wire. ## Practical checks - Before blaming ZAP for a request the target rejected, look at whether the length header was recomputed for a body your own configuration rewrote afterwards. - If a scan appears to send far more requests than its rules should, check whether anti-CSRF handling is on; the refresh doubles some rules' traffic. - If a redirect took the scan somewhere surprising, the redirect validator only keeps hops inside the scan's scope — so the question to ask is what scope the run was given, not whether redirects were followed.

  • Why strip the conditional request headers at all?
    The rule's message is cloned from a recorded request, which may carry `If-Modified-Since` or `If-None-Match`. Left in place the target can answer with a not-modified status and no body, and the rule would then judge an empty response. Clearing them forces a full answer every time.
  • Does the rule's message count equal the number of payloads it sent?
    Not necessarily. The count is incremented for every message that returns through the send path, and anti-CSRF token regeneration can issue an extra request on a rule's behalf. Treat the count as traffic sent, not as payloads tried.
  • How do you attribute scan traffic in the target's own access log?
    Enable `scanner.pluginHeader`, which is off by default. Every active-scan request then carries a header naming the rule id that sent it. Match it case-insensitively, since HTTP header names are case-insensitive and the constant is spelled in lower case.

saying these in an interview costs you the question

  • Says the rule edits and sends the recorded message itself
  • Assumes the scan-id header is present by default
  • Thinks a rule's message count equals its payload count
  • Says active rules each open their own connection to the target
  • Claims redirects are followed without any scope check