Why attacks sometimes look like normal traffic

Posted by:

(This post is co-authored with Rishi Raj Jain)

The more closely an attack resembles your normal traffic, the harder it is to stop. A spoofed packet flood is easy to drop precisely because nothing legitimate resembles it. When attack traffic uses valid requests, real IP addresses, and standard protocol behavior, your filters face a harder problem: you cannot block what you cannot distinguish.

This post explores where that split happens in the network stack and how it should shape the defenses you build. We'll compare two broad categories of attacks: those that expose clear signals at L3/4 and those that hide behind valid application traffic at L7. We'll also look at four real incidents that show the difference: Slowloris in 2009, Mēris in 2021, HTTP/2 Rapid Reset in 2023, and an a lesser-known attack that can crash a server while leaving nothing in your access logs, the kind of attack you cannot even see, let alone block.

Layer numbers are shorthand for filter cost

signal versus intent across the stack

At L3/4, spoofed SYN floods, memcached reflection bursts, and UDP amplification patterns all leave measurable signals: bandwidth spikes, impossible source behavior, and protocol states with no benign equivalent. Those signals appear before application code runs, which means you can make a drop decision at the edge cheaply before spending anything on TLS termination or HTTP parsing.

At L7, a botnet request and a customer request during a flash sale can be byte-for-byte identical on the wire. The signal you need is intent, and reading intent requires decrypting traffic, parsing requests, and comparing behavior against a baseline, all of which costs CPU before you can confidently block anything. That cost asymmetry is what makes L7 harder, and it grows as bots get better at mimicking real browsers.

Attack size is a poor measure of difficulty

When someone quotes an attack in terabits per second, the instinct is to reach for bandwidth planning and scrubbing capacity. That works for volumetric floods, but size is a weak proxy for mitigation difficulty once traffic looks application-shaped.

Common assumption Why it breaks
A larger attack is harder to stop Slowloris took down servers with a single laptop and near-zero bandwidth. The server ran out of connection slots, not capacity.
More edge capacity solves modern DDoS Mēris sent 21.8 million valid HTTP requests per second from real devices. No amount of capacity tells you which ones to drop.
Broken packets are the main filter signal HTTP/2 Rapid Reset used only valid RST_STREAM frames. Every packet was protocol-compliant, with nothing malformed to catch.

What signals look like at each layer

Knowing which layer an attack targets tells you where in the stack you need to act. The more important question is what signal you have available at that point, because its quality determines whether you can make a fast, cheap drop decision or whether you are stuck doing expensive inspection work before you can act at all.

Layer focus Primary signal type Typical measurement Filter cost
L3 volumetric Bandwidth and reflection patterns Bits per second Low at provider edge
L4 protocol Connection state exhaustion Packets per second Low to moderate with stateless techniques
L7 application Request validity and behavior Work per request at origin High due to TLS and parsing

L3 and L4: measurable signals

Volumetric and protocol attacks expose signals you can measure before application code runs, before you incur the cost of TLS termination and HTTP parsing. Common examples include sudden bandwidth spikes, spoofed or implausible source addresses, SYN packets that never complete a handshake, and fragmented or repetitive protocol patterns with no real client equivalent. These signals are visible early, which is why provider-scale scrubbing, anycast distribution, and stateless mitigations like SYN cookies remain effective against floods that would overwhelm an origin if they arrived unfiltered.

L7: interpreted signals

Application-layer attacks require you to decrypt traffic, parse requests, and compare behavior against a baseline for your specific service. That inspection costs you resources before you can confidently drop anything, while the attacker may have spent very little to generate each request.

Volumetric floods are measured in bits per second, protocol floods in packets per second, and application floods in the ratio between request cost at the client and request cost at your origin. A single search, login, or checkout can cost the server orders of magnitude more than it cost to send, which makes L7 abuse as much an economics problem as a networking problem.

L3/4 mitigation is largely a solved problem at the edge

“Solved” does not mean attacks have gotten smaller, and it does not mean the supply of attack infrastructure is shrinking: every newly misconfigured server exposed to the internet becomes another reflector that an attacker can bounce amplified traffic through. What is solved, if you sit behind serious network protection, is the classification problem. This traffic looks nothing like a legitimate user session, so you can drop it on cheap signals alone without ever reasoning about intent.

GitHub’s February 2018 memcached incident shows that clarity at extreme scale. The attack peaked at 1.35 Tbps and 126.9 million packets per second, generated by tens of thousands of reflectors. The service recovered within minutes after GitHub routed traffic through Akamai scrubbing. The signal was obvious: a 15-byte UDP request to an exposed memcached instance could produce responses up to 51,000 times the request size. Nothing in normal application traffic resembles that pattern.

L3/4 attacks fall into two families. Volumetric attacks fill bandwidth through reflection across UDP-based services (DNS, NTP, memcached, ICMP) and are measured in bits per second. Protocol attacks exhaust state in firewalls, load balancers, and servers through SYN floods, ACK floods, and fragment abuse, and are measured in packets per second. Neither family tries to impersonate a legitimate browser traffic.

Attack family Representative vectors Primary measurement What makes it easy to filter Common mitigations
Volumetric UDP reflection, DNS/NTP/memcached amplification, ICMP floods Bits per second No legitimate user session produces this traffic shape Anycast, provider scrubbing, raw edge capacity
Protocol SYN floods, ACK floods, fragment abuse Packets per second Half-open or impossible handshake behavior SYN cookies, stateless drop rules, connection limits

Spoofed sources, half-open handshakes, and reflection floods have no legitimate twin in production traffic, so dropping them rarely harms real users when rules are scoped correctly.

If you run bare metal directly on the public internet without scrubbing in front of it, even well-understood attack classes can overwhelm you because the classification and absorption work has nowhere else to happen.

L7 is a different problem entirely.

L7 mitigation is harder in production

A single HTTP request can appear completely normal while still being part of an attack campaign. The same GET /products/12841 from a distributed botnet and one from a customer during a promotion can be identical on the wire. Packet signatures are useless here. Behavioral context and baselines are what separate the two.

Several structural factors make that harder in practice:

  • Inspection cost: TLS termination and HTTP parsing consume defender CPU before a confident block decision is possible, while attackers generate requests cheaply at scale.
  • Moving baseline: Real audiences are bursty. Launches and viral links can reproduce the shape of an attack without any malicious intent.
  • Uneven endpoint cost: Applying the same rate limit to a static page and to a login or checkout flow can block humans on cheap routes while leaving expensive routes exposed.
  • Realistic bots: Headless browsers, residential proxies, and realistic TLS client behavior mean simple header checks are no longer a durable strategy.

Let’s look at four attacks that worked in different ways but bypassed the cheap filters for the same reason, which is that none of them looked like an attack at the layer those filters watch.

1. HTTP/2 CONTINUATION Flood and the invisible request

HTTP/2 lets a client split request headers across several frames: an opening HEADERS frame followed by one or more CONTINUATION frames, with an END_HEADERS flag marking the end of the block. The CONTINUATION Flood, disclosed in April 2024 by researcher Bartek Nowotarski, abuses this by sending an unbounded stream of CONTINUATION frames and never setting END_HEADERS. The server keeps reading and buffering the header block, waiting for an end that never comes, and a single connection can drive memory to an out-of-memory crash.

http/2 continuation flood

Because the request never completes, most servers never write it to the access log, so the server can go down while your dashboards and logs see nothing. There is no slow client to flag, no malformed frame to match, and no finished request to rate-limit. The fix is to cap the number of CONTINUATION frames per stream and the total header size, then close connections that exceed those limits.

2. Slowloris and low-bandwidth exhaustion

Robert Hansen (RSnake) released Slowloris in June 2009 to show that application availability could be destroyed without a volumetric flood. The technique uses one machine to open many connections, send partial HTTP headers, and drip additional header lines slowly enough to prevent timeouts while never completing a request. The server holds each connection open while waiting for completion. Once the pool is full, legitimate clients cannot establish new connections.

The attack uses almost no bandwidth, so volume-based defenses see nothing alarming. Each connection looks like a slow mobile client on a poor network rather than a flood. Effective mitigation is behavioral: connection and header timeouts, per-IP connection limits, and reverse proxies that buffer and terminate incomplete requests before they tie up application threads.

slowloris connection exhaution mechanics

Slowloris separates attack impact from attack size. When your dashboards only watch bandwidth, this class of abuse can look healthy right up until the connection pool is gone.

3. Mēris and valid requests at record scale

In September 2021, the Mēris botnet (named after the Latvian word for plague) struck Yandex at 21.8 million requests per second, a record at the time. The ramp was gradual rather than a sudden cliff, and each increase could be mistaken for organic growth until source and behavior context made the pattern clear.

Mēris was built from compromised MikroTik routers exploited through CVE-2018-14847, a vulnerability patched in 2018 that remained present on many devices in the wild. Research from Yandex and Qrator Labs estimated the full compromised population at around 250,000 hosts, while individual attacks used only a fraction of those hosts at a time. The attack traffic consisted of complete valid HTTP requests sent at enormous volume. Bots used HTTP pipelining to send multiple requests down a single connection without waiting for responses, and SOCKS proxies obscured origin behavior. Because the sources were real network devices on residential and business networks, each host looked like an ordinary client, and each request looked like legitimate HTTP.

meris request pipelining across compromised routers

When every request is syntactically valid, you rely on rate control, fingerprinting, reputation, and behavioral baselines. You also need a clear picture of what normal growth looks like for your service because a botnet spike and a successful launch can look similar in raw request rate.

4. HTTP/2 Rapid Reset and protocol-valid abuse

HTTP/2 Rapid Reset (CVE-2023-44487) was disclosed on October 10, 2023, through coordinated publication by Google, Cloudflare, and AWS after exploitation in the wild between August and October of that year. Google reported a peak of 398 million requests per second: roughly 7.5 times the previous record. Cloudflare observed about 201 million requests per second, generated by a botnet of approximately 20,000 machines.

http/2 rapid reset open-and-cancel cycle

HTTP/2 multiplexes many streams over one TCP connection and lets clients cancel individual streams with RST_STREAM frames: a legitimate feature real browsers use, for example, when aborting image loads after scrolling. Attackers repeatedly opened a stream, canceled it immediately, and repeated the cycle. The attacker incurred very little cost while the server paid the setup and teardown cost for each cycle. Every frame was valid HTTP/2, so there was no malformed packet to match and no simple signature beyond resource accounting behavior.

Concurrent stream limits alone do not solve the problem because abusive clients can churn cancellations faster than servers reclaim state in some architectures. Mitigation focused on protocol accounting changes: reset counting per connection, tighter stream limits, and earlier connection closure for abusive patterns. The fix was resource accounting: counting resets per connection, tightening stream limits, and closing connections earlier when abuse patterns appear.

Taken together, all four attacks follow the same playbook.

The shared pattern across all four attacks

These attacks differ in speed, bandwidth profile, and protocol details, but they all become hard to stop at the same point: when the cheapest filters cannot tell them apart from normal traffic, or cannot see them at all.

Dimension Slowloris (2009) Mēris (2021) Rapid Reset (2023) CONTINUATION Flood (2024)
Speed profile Very slow Fast Very fast Fast
Bandwidth footprint Almost none High High but cheap to generate Almost none
Unit of abuse Valid slow request Valid full request Valid canceled stream Endless header frames
Why it evades cheap filters No volume anomaly Resembles organic traffic No malformed frame to match Never completes, never logged
Durable mitigation theme Connection behavior controls Reputation, rate limits, fingerprints Protocol accounting and limits Header size and frame-count limits
how four attacks hide above inexpensive filter layers

This pattern explains why raw capacity stops helping once attacks resemble normal traffic. Peak volumes may keep climbing, but defense now depends on behavior profiling, rate control, and protocol limits rather than more bandwidth.

Practical strategies for L7 defense

No single WAF rule or rate limit covers every L7 attack pattern. Most teams stack several controls and accept that each one can misclassify some traffic. The goal is to block abuse efficiently without breaking legitimate spikes.

layered l7 defense

Some common mitigations that handle most of the work:

  • Tier your rate limits: Set a blanket per-IP limit on a rolling window, then tighten it on compute-heavy routes such as login, search, checkout, and signup.
  • Order rules cheapest first: Run IP reputation, geo constraints, and coarse heuristics before bot inspection and application logic, so most unwanted traffic is dropped before it reaches a costly control. Bunny Shield’s bot detection layers checks in this order: cheap request-integrity, IP, and ASN reputation checks run first, and you can escalate to header-anomaly and full fingerprint validation by moving from the Low to the High sensitivity profile or by toggling individual checks in Custom mode.
  • Add an adaptive baseline: Track what your normal traffic looks like over time and filter what does not fit. For the challenge layer, Bunny Shield’s DDoS protection provides a JavaScript proof-of-work challenge you can scope to specific endpoints or run in Always-On Mode. Challenges raise attacker cost but add user friction and become less effective as automation improves. The open-source Anubis proxy gates bots behind a SHA-256 puzzle and is used by groups like UNESCO and GNOME, yet researchers note the compute cost is negligible for a well-funded scraper, so it works best as one added layer.
  • Harden the application and protocol: Set request, header, and absolute connection timeouts, enforce minimum ingress data rates to defeat slow-send attacks, and cap HTTP/2 streams with reset counting. See OWASP’s Denial of Service Cheat Sheet for the full list. Many of these gaps are invisible to firewall signatures.

Where this is heading

How difficult an attack is to stop depends on how different it looks from your legitimate users. L3/4 remains tractable at the edge because benign production traffic rarely shares the shape of floods and reflection abuse. L7 remains difficult because benign and malicious traffic can share the same protocol, the same endpoints, and the same request structure.

As bots continue to improve at impersonating real clients through residential IP diversity, realistic TLS stacks, and more complete page interaction patterns, the right question shifts from how many requests arrived to who is sending them and whether that pattern fits your service.

No single layer solves the whole problem, so a complete defense needs both. Network-level DDoS protection absorbs and drops obvious floods at the edge, while application-aware controls inspect traffic that looks like real users. As you design your own setup, the rule that holds up over time is to filter on clear signals as early and as cheaply as you can, then fall back on behavior and baselines only for the requests that look fine on their own.