Everything built so far in this unit watches a pattern of behaviour: a repeated request, a rate, a command fanned out to many machines. This article's detector is different in kind. It looks for one exact, known signature, and nothing else.
Analogy: The night guard's checklist
A night guard does not memorise every possible crime; they are given a short, specific checklist, a side door that should always be locked, a loading bay that should be empty after 9 p.m., and they raise an alarm the moment reality disagrees with the checklist. A signature-based intrusion detection rule works the same way: a precise pattern to match against traffic, cheap to check, and reliable for exactly the one thing it was written to catch.
A single rule, three lines
simple_ids_rule.py
# simple_ids_rule.py: flag traffic matching a known-bad pattern
import dpkt, socket
from open_capture import read_packets
SUSPICIOUS_UA = b"sqlmap" # a well-known SQL injection testing tool's signature
for ts, raw in read_packets("capture.pcap"):
try:
ip = dpkt.ethernet.Ethernet(raw).data
tcp = ip.data
if SUSPICIOUS_UA in tcp.data:
src = socket.inet_ntoa(ip.src)
print(f"ALERT: sqlmap signature seen from {src}")
except AttributeError:
continue
Run against a lab capture containing two requests that carry sqlmap's default User-Agent string, mixed in among an ordinary browser request to the same server:
Sample output
ALERT: sqlmap signature seen from 203.0.113.44
ALERT: sqlmap signature seen from 203.0.113.44
The ordinary browser request, carrying User-Agent: Mozilla/5.0 and nothing resembling sqlmap, never appears. SUSPICIOUS_UA in tcp.data is a plain substring search across the raw payload bytes, which is why the rule needs no HTTP parsing at all: it does not care whether the six bytes it is looking for sit in a header, a URL, or anywhere else in the packet.
Caution: A rule this exact is also this brittle
sqlmap ships this identifying string by default, which is precisely why it is such a clean teaching example: the tool announces itself. A single configuration change, --user-agent set to anything else, defeats this exact rule completely while the underlying SQL injection attempt continues unaffected. A signature only ever catches what it was written to catch, nothing adjacent to it.
Two kinds of detection, built to work together
This rule and the sliding-window detector from the last article look for completely different things, and that difference is deliberate.
| Signature-based (this article) | Anomaly-based (the sliding window) | |
|---|---|---|
| Detects | A known, specific pattern | A deviation from a normal baseline |
| Strength | Very low false positives for what it targets | Catches attacks nobody has named yet |
| Weakness | Blind to anything not in its rule set | Needs a believable baseline, and tuning |
| Real tool | Snort, Suricata | A SIEM's statistical alerting |
Why it carries weight: Why both, not either
A mature defence runs both kinds side by side, because each catches what the other misses. The signature rule in this article would never have noticed the flood two articles ago, because a flood of ordinary-looking GET /index.html requests matches no known-bad string. The sliding-window detector, in turn, would never have noticed a single sqlmap probe buried among normal traffic, because one request changes no rate at all. Together they cover far more ground than either does alone.
Key takeaway: Key takeaway
A signature rule is the night guard's checklist: narrow, cheap, and exactly right for the one thing it names, blind to everything else. Paired with a rate-based detector like the sliding window from the last article, the two together form a complete, complementary defence, which is how real tools such as Snort and a SIEM's anomaly alerting are actually used side by side.
Check your understanding
Question 1: Why does simple_ids_rule.py not need to parse the packet as HTTP at all, unlike find_flood_pattern.py?
It only searches for a fixed byte string anywhere in the raw TCP payload, which works regardless of where in the packet that string sits. find_flood_pattern.py needs to parse the payload as an HTTP request because it needs specific structured fields, the method and the URI, not just a yes-or-no match.
Question 2: What is the strength of a signature-based rule, and what is its corresponding weakness?
Its strength is a very low false-positive rate for exactly what it targets: if the signature matches, you can be confident about what triggered it. Its weakness is that it is blind to anything not already in its rule set; a trivial change to the attacker's tooling, such as a different User-Agent, defeats it completely.
Question 3: Why would a mature defence run signature-based and anomaly-based detection together rather than choosing one?
Each catches attacks the other would miss. A signature rule would never notice a flood of requests that each individually look completely ordinary, since nothing in any single request matches a known-bad pattern. A rate-based detector would never notice a single probe using a known attack tool, since one request changes no rate at all. Running both covers both kinds of threat.

