Web Security

What Is Denial of Service? DoS and DDoS Attacks Explained

A complete technical guide to denial of service attacks: resource exhaustion, amplification, application-layer DoS, and how to detect, mitigate, and prevent service disruption.

Intermediate12 min·Nyxeara Security Research·2026-09-16·CWE-400
denial-of-serviceweb-securitydosddosavailability
Prerequisites
what-is-http

Short answer

A denial of service (DoS) attack makes a service unavailable to its intended users by overwhelming it with traffic, exhausting its resources, or exploiting a vulnerability that causes it to crash. A distributed denial of service (DDoS) attack amplifies this by using thousands of compromised machines — a botnet — to generate traffic from many sources simultaneously, making it far harder to block.

The idea in one minute

Imagine a small coffee shop with a single barista. One morning, twenty people walk in at the same time and each orders an elaborate, customized drink — oat milk, extra shot, half-caff, extra hot, no foam, in a mug, with a side of honey. The barista can only make one drink at a time. The line grows. Customers who just wanted a simple black coffee walk out. Now imagine that instead of twenty people, two thousand people show up. The shop is physically full. No one can even reach the counter. The barista gives up.

A DoS attack is that crowd. In a DDoS, the crowd is coordinated — each person in line was instructed to arrive at exactly 9:00 AM and order the most complicated drink on the menu. The attacker doesn't need to be in the shop; they just need to control enough people (compromised machines in a botnet) to issue the instructions. The shop's defenses — a bigger barista, a shorter menu, a bouncer at the door — map to rate limiting, resource limits, and traffic filtering.

How denial of service actually works

DoS and DDoS attacks operate at different layers of the OSI model, each targeting a different resource bottleneck:

| Layer | Attack type | Resource targeted | Example | |---|---|---|---| | L3 – Network | Volumetric | Bandwidth | UDP flood, ICMP flood, DNS amplification | | L4 – Transport | Protocol | Connection state | SYN flood, SYN-ACK flood, connection exhaustion | | L7 – Application | Resource | CPU, memory, database, disk | HTTP flood, slow loris, ReDoS, hash collision |

Volumetric attacks (L3). These saturate the network pipe. The attacker sends more traffic than the target's internet connection can handle. The most efficient volumetric attacks use amplification: the attacker sends a small query to an open UDP server (DNS, NTP, SSDP, Memcached) with a forged source IP — the target's IP. The server sends a much larger response to the target. A single 64-byte DNS query can generate a 4,000-byte response, a 1:62 amplification ratio. An NTP monlist query can amplify 1:556. A Memcached UDP reflection, before it was largely mitigated, achieved 1:51,000.

Protocol attacks (L4). These exploit how the TCP protocol maintains state. A SYN flood sends a flood of TCP SYN (connection request) packets with spoofed source IPs. The server allocates memory for each half-open connection and sends back a SYN-ACK, waiting for the final ACK that never arrives. The connection table fills up, and legitimate connections are dropped. A single server can handle roughly 65,535 concurrent TCP connections (the ephemeral port limit), and beyond that, new connections fail.

Application-layer attacks (L7). These are the most sophisticated and hardest to mitigate because they use legitimate-looking HTTP requests:

  • HTTP flood: thousands of requests for a resource-intensive endpoint (a search query, a report generator, an image processing endpoint). Each request is a valid HTTP GET or POST that passes every application-level check — but the cumulative load overwhelms the server.
  • Slow loris: the attacker opens many connections to the server but sends data very slowly — a few bytes at a time, never completing the HTTP request headers. The server keeps each connection open, waiting for the full request. Eventually the server's connection pool is exhausted.
  • ReDoS (Regular Expression Denial of Service): a carefully crafted input triggers catastrophic backtracking in a regular expression. A regex like ^(a+)+$ on input "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!" causes the engine to try an exponential number of paths — milliseconds of CPU becomes seconds, then minutes.
  • Hash collision: sending many HTTP POST parameters or JSON keys that hash to the same bucket in the server's hash table, turning an O(1) lookup into an O(n²) insertion. This was famously exploited against the Django and Rails frameworks in 2011.

DDoS amplification. A DDoS is a DoS from many sources — typically a botnet of compromised IoT devices, home routers, or servers. The 2016 Mirai botnet, composed primarily of insecure IP cameras and DVRs, launched a 1.2 Tbps DDoS against DNS provider Dyn, taking down Twitter, Netflix, Reddit, and other major sites on the US East Coast. The botnet's source code was released publicly, and variants continue to power the majority of large DDoS attacks today.

The request flow

A DNS amplification DDoS:

Attacker ──controls──▶ Botnet (thousands of compromised devices)
          │
          ▼
    Each bot sends a small DNS query (64 bytes)
    with spoofed source IP = victim's IP
    to an open DNS resolver
          │
          ▼
    DNS resolver sends large response (~4,000 bytes)
    to victim's IP
          │
          ▼
    Victim's network pipe is saturated
    Legitimate traffic cannot reach the server
sequenceDiagram
    participant Attacker
    participant Bot as Botnet (1,000 bots)
    participant DNS as Open DNS Resolvers
    participant Victim

    Attacker->>Bot: Launch attack on victim IP
    par Each bot queries multiple resolvers
        Bot->>DNS: 64B query (src=Victim)
        Bot->>DNS: 64B query (src=Victim)
        Bot->>DNS: 64B query (src=Victim)
    end
    par Each resolver amplifies
        DNS-->>Victim: 4,000B response (62× amplification)
        DNS-->>Victim: 4,000B response
        DNS-->>Victim: 4,000B response
    end
    Note over Victim: 64 KB inbound → 4 MB outbound<br/>1,000 bots × 3 resolvers × 4 KB = 12 MB per round<br/>Saturates 100 Mbps link in seconds

A minimal example

An application-layer DoS via an expensive endpoint with no rate limiting:

from flask import Flask, request, jsonify
import time

app = Flask(__name__)

# Simulates an expensive database aggregation
@app.route("/api/reports/sales")
def sales_report():
    start_date = request.args.get("start", "1970-01-01")
    end_date = request.args.get("end", "2099-12-31")

    # CPU-intensive: full table scan with aggregation
    # (simulated with a sleep — real equivalent would be a heavy DB query)
    time.sleep(2)

    return jsonify({"status": "report generated"}), 200

A single attacker with a laptop can open 100 concurrent connections to this endpoint and consume 200 thread-seconds of server capacity. On a server with 4 worker processes, the application is effectively down after 4 concurrent requests.

A slow loris attack in Python:

import socket

target = ("victim.com", 80)

# Open many connections and send headers very slowly
for _ in range(200):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(target)
    s.send(b"GET / HTTP/1.1\r\n")
    s.send(b"Host: victim.com\r\n")
    # Never send the final \r\n — server waits indefinitely
    # Optionally send one header byte every 30 seconds

A resilient version with rate limiting, request timeouts, and resource caps:

from flask import Flask, request, jsonify, abort
from flask_limiter import Limiter
import time
import re

app = Flask(__name__)
limiter = Limiter(app, key_func=lambda: request.remote_addr)

# Rate limit: 10 requests per minute per IP on expensive endpoints
@app.route("/api/reports/sales")
@limiter.limit("10 per minute")
def sales_report():
    start_date = request.args.get("start", "1970-01-01")
    end_date = request.args.get("end", "2099-12-31")

    # Validate date format before querying
    if not re.match(r"^\d{4}-\d{2}-\d{2}$", start_date):
        return jsonify({"error": "invalid date"}), 400

    # Use server-side timeout for expensive operations
    import signal

    def handler(signum, frame):
        abort(504)  # Gateway Timeout

    signal.alarm(30)  # 30-second hard limit

    result = run_expensive_query(start_date, end_date)

    signal.alarm(0)  # Cancel alarm
    return jsonify(result), 200


# Global rate limit: reject when queue depth exceeds threshold
app.before_request
def check_load():
    # Pseudocode — real implementation queries the task queue depth
    if get_current_queue_depth() > 100:
        abort(503)  # Service Unavailable

The key difference: every expensive operation is rate-limited per IP, bounded by a hard timeout, and protected by a global circuit breaker that returns 503 before the server falls over.

Why the vulnerability exists

  • Internet connections have finite bandwidth, and servers have finite resources. This is not a bug — it's physics. The vulnerability is the absence of defenses that acknowledge these limits and enforce boundaries.
  • Amplification is inherent in UDP protocols. DNS, NTP, SSDP, Memcached, and other UDP-based services respond to queries with responses that are often dramatically larger than the query. This behavior is designed for efficiency, not attack resistance. Source IP spoofing makes amplification attacks practical.
  • Application-layer attacks look like legitimate traffic. A flood of GET /search?q=test requests from 10,000 distinct IPs is indistinguishable from a viral traffic spike without behavioral analysis. Rate limiting by IP alone is ineffective against a large botnet — each IP makes only a few requests.
  • Connection state is expensive. The kernel's TCP connection table, the application's thread pool, and the database connection pool all have hard limits. Slow loris exploits the gap between "connection established" and "request complete" — the server cannot distinguish a slow client from an attacker.
  • Asymmetric resource consumption. Many operations cost significantly more on the server than on the client. A database query that takes 500ms of CPU time on the server is triggered by a single HTTP request that took microseconds to send. An image resize that consumes 200MB of memory is triggered by a 100KB upload. Attackers exploit every such asymmetry.
  • IoT insecurity scaled. The Mirai botnet demonstrated that hundreds of thousands of internet-connected devices ship with hardcoded credentials, no update mechanism, and open telnet ports. These devices are permanently available for recruitment into botnets. The problem has not been solved — it has grown.

What attackers look for

  • Amplification-capable protocols: open DNS resolvers, NTP servers with monlist enabled, SSDP-enabled UPnP devices, and Memcached servers exposed to the internet. Tools like shodan.io and censys.io make finding these trivial.
  • Expensive API endpoints: search, report generation, data export, image processing, PDF generation, any endpoint that makes multiple database queries or calls external services. A single GET /api/export?format=pdf that consumes 5 seconds of CPU per request is a force multiplier for an attacker.
  • Endpoints with no caching: every request hits the database or triggers computation. If there is no Cache-Control header, no CDN, and no response-level caching, the attacker directly drives load to the origin server.
  • Slow loris targets: servers with small connection pools, permissive Keep-Alive timeouts, and no reverse proxy (like nginx) in front. Apache's default Timeout of 300 seconds and KeepAliveTimeout of 5 seconds make it particularly vulnerable.
  • Hash collision vectors: endpoints that parse user-supplied data into hash tables — JSON bodies, POST parameters, multipart form data — especially in languages where hash table implementations are not randomized (PHP, Python 2, Ruby).
  • Unpatchable IoT devices: the Shodan search "2303" "password" (referencing the default telnet credentials of Mirai-vulnerable devices) still returns hundreds of thousands of results years after Mirai's source code was released.

Detection

  • Network-level: monitor inbound traffic volume in bits per second (bps), packets per second (pps), and connections per second (cps) at the network edge. A sudden 10× increase in any metric that does not correlate with a legitimate traffic source (marketing campaign, product launch) indicates a volumetric attack.
  • Connection-level: monitor the ratio of SYN to SYN-ACK to ACK packets. A high SYN rate with low SYN-ACK completion rate indicates a SYN flood — the server is responding to connections that never complete.
  • Application-level: monitor response latency percentiles (p50, p95, p99), error rates (4xx, 5xx), and request queue depth. A rising p99 latency with a flat p50 latency suggests a small number of expensive requests are degrading service — possibly an application-layer DoS targeting a specific endpoint.
  • Log analysis: look for repeated requests to the same expensive endpoint from many distinct IPs, requests with unusually long completion times (slow loris), or requests containing inputs that trigger regex backtracking.
  • ReDoS detection: deploy a regex input validator that rejects patterns exceeding a complexity threshold (e.g., more than 3 nested quantifiers or a quantified group containing another quantifier). Test regex patterns against known ReDoS payloads using tools like regex101's debugger or rxxr2.

Verification: real vulnerability or false positive?

A finding is confirmed when:

  • The target service becomes unreachable or returns errors (connection refused, timeout, 503) under the attack load, and returns to normal after the attack stops. This confirms the resource exhaustion is real and not pre-existing capacity issues.
  • A single inexpensive client (a laptop or a single cloud VM) can render the service unavailable for other users — this confirms a true vulnerability rather than a "sufficiently large DDoS will take anything down" truism. The bar is: can a low-cost attacker (under $100 of cloud compute) degrade the service?
  • An amplification factor is measurable: the attack traffic volume at the target exceeds the traffic volume generated by the attacker by a factor greater than 1. A factor of 10+ is significant; a factor of 100+ is critical.
  • A ReDoS payload causes the server's CPU to spike to 100% and the response time to increase from milliseconds to tens of seconds — confirmed by comparing response times with and without the payload.
  • A slow loris test shows that a small number of connections (under 200) can exhaust the server's connection pool and prevent new, legitimate connections — confirmed by attempting a legitimate request from another client during the attack and receiving a timeout or connection refused.

An endpoint that is CPU-intensive under normal conditions but has caching, rate limiting, or autoscaling is partially mitigated, not a false positive — document the existing controls and evaluate whether the rate limit threshold is low enough to prevent resource exhaustion within the autoscaling lag time.

Real-world impact

  • Dyn DNS outage (2016). The Mirai botnet, estimated at 500,000 compromised devices, launched three waves of DDoS attacks against Dyn, a major DNS provider. Each wave peaked at over 1 Tbps. The attack rendered Dyn's DNS resolution unavailable for much of the US East Coast, taking down Twitter, Netflix, Reddit, Spotify, Etsy, GitHub, SoundCloud, and The New York Times. Users could not resolve these sites' domain names and saw "DNS server not responding" errors. The attack demonstrated that a single vulnerable device type — IP cameras with default passwords — could disrupt the internet's core infrastructure.
  • GitHub DDoS (2018). GitHub was hit by a 1.35 Tbps DDoS attack — the largest recorded at that time — originating from over 1,000 autonomous systems and 100 million unique source IPs. The attack used Memcached UDP amplification (1:51,000 ratio). GitHub mitigated the attack in under 10 minutes by rerouting traffic through Akamai's DDoS mitigation service. The attack lasted about 20 minutes total. The key lesson: having a DDoS mitigation provider and automated traffic rerouting is essential — without Akamai, GitHub's own infrastructure would have been saturated in seconds.
  • Application-layer attacks on financial services. In 2022, multiple large banks experienced sustained application-layer DDoS attacks lasting several days. The attacks targeted login endpoints with low-and-slow HTTP requests designed to exhaust application server resources while staying below volumetric thresholds. Mitigation required behavioral analysis to distinguish attacker traffic from legitimate user traffic — IP-based rate limiting was ineffective because each attacker IP made very few requests.
  • Smaller targets, larger relative impact. While major DDoS attacks on large tech companies make headlines, the majority of DoS attacks target small-to-medium businesses, gaming servers, and independent services. A 10 Gbps attack, trivial to generate using a rented botnet ($20–$100 on underground markets), is enough to saturate most small business internet connections (100 Mbps–1 Gbps). For these targets, a DDoS attack can mean days of downtime, lost revenue, and permanent reputational damage.
  • Ransom DDoS (RDoS). Attackers now commonly send a ransom note threatening a DDoS attack unless paid, often with a small proof-of-concept attack to demonstrate capability. The ransom is typically demanded in cryptocurrency, with amounts ranging from a few hundred dollars for small businesses to tens of thousands for larger enterprises. Paying does not guarantee the attack will not happen — many groups re-extort the same targets.

Prevention

  • Use a DDoS mitigation service. Cloudflare, Akamai, AWS Shield, and Google Cloud Armor provide network-level and application-level DDoS protection. Their networks have the bandwidth to absorb large volumetric attacks and the traffic analysis capability to filter application-layer attacks. AWS Shield Advanced provides automatic mitigation for AWS-hosted resources and financial protection against scaling costs during an attack.
  • Rate limit at every layer. Apply per-IP, per-session, and per-endpoint rate limits. Use sliding window counters rather than fixed windows to avoid burst allowance. Set limits based on the endpoint's resource cost — a static asset can handle 1,000 req/s; a report generator may need a limit of 1 req/s per IP.
  • Set connection timeouts aggressively. Configure the web server and reverse proxy to timeout idle connections quickly. nginx's client_header_timeout, client_body_timeout, and keepalive_timeout should be 10–30 seconds, not minutes. Apache's TimeOut directive should be similarly restricted.
  • Use a reverse proxy. Place nginx, HAProxy, or a cloud load balancer in front of the application server. The reverse proxy handles connection termination, request buffering, and can reject slow loris attacks before they reach the application. Most reverse proxies also provide built-in rate limiting.
  • Implement a Web Application Firewall (WAF). A WAF can detect and block application-layer DoS patterns: repeated requests to expensive endpoints, requests with unusually slow transfer rates, and requests matching known ReDoS or hash-collision payloads. WAF rules must be tuned to avoid false positives — a flash crowd (legitimate traffic surge) looks similar to an application-layer attack.
  • Set resource limits on the application server. Restrict the number of concurrent requests per worker process, the maximum request body size, the maximum URL length, and the maximum execution time per request. These limits prevent any single request from consuming unbounded resources.
  • Use caching aggressively. Cache responses at the CDN, reverse proxy, and application levels. A cached response costs nothing to serve — it is the single most effective defense against application-layer DoS. Set appropriate Cache-Control headers and use a CDN with edge caching for all cacheable content.
  • Implement circuit breakers. Monitor error rates and request latency, and automatically stop routing traffic to a service tier when it degrades below a threshold. The circuit breaker returns a fast 503 instead of letting requests pile up in the queue, allowing the service to recover.
  • Disable UDP amplification. Close unnecessary UDP ports on internet-facing servers. Restrict DNS recursion to trusted clients. Disable NTP monlist. Block outbound Memcached traffic at the network edge. The fewer amplifiers available to attackers, the harder it is to launch a large volumetric attack.
  • Use SYN cookies. SYN cookies allow the server to avoid allocating connection state until the three-way handshake completes. This is enabled by default in Linux (net.ipv4.tcp_syncookies = 1) and prevents SYN flood-based connection table exhaustion.

Related vulnerabilities

  • XXE — XML external entity injection can be used for DoS via the "billion laughs" attack (an XML entity that expands exponentially, consuming memory until the parser crashes or OOMs).
  • SSRF — SSRF can be used to target internal services that are more vulnerable to resource exhaustion, or to amplify traffic by chaining SSRF vectors across multiple internal hosts.
  • Authentication vulnerabilities — weak authentication on admin panels exposed to the internet allows attackers to reconfigure firewall rules, disable rate limiting, or provision additional cloud resources that are then used for DDoS attacks.
  • Command injection — a command injection vulnerability on a server running a botnet client can be used to add the server to the botnet, turning it into a DDoS participant.

Testing methodology (do this safely)

  • Do not test DoS or DDoS attacks against production systems, third-party services, or any system you do not own and operate exclusively in a test environment. Volumetric testing against production is itself a denial of service. Most cloud providers prohibit load testing above a certain threshold without prior approval.
  • For application-layer DoS testing, use a staging environment that mirrors production capacity. Send increasing request rates to a specific endpoint and measure the rate at which latency increases and error rates rise. The point at which the p99 latency exceeds 5x the baseline or error rates exceed 1% is the "breaking point" — document this as the endpoint's sustainable capacity.
  • For slow loris testing, set up a test web server and open connections with slow send rates (1 byte per 10 seconds). Measure the number of concurrent connections that exhaust the connection pool. Use this to determine the appropriate keepalive_timeout and worker connection limits.
  • For ReDoS testing, compile a list of known regex patterns in the codebase and test each against payloads designed to trigger catastrophic backtracking (e.g., "a" * 30 + "!" for ^(a+)+$). Use a timeout wrapper to ensure tests cannot hang.
  • Never use amplification testing (DNS, NTP, Memcached) — this requires sending spoofed traffic, which is illegal in most jurisdictions and harms the amplifier's operator. Instead, measure the amplification factor by analyzing protocol response sizes in a controlled lab environment.
  • Document the test environment configuration (CPU, memory, network bandwidth, concurrent connection limit), the attack configuration (number of sources, request rate, payload size), and the observed results (latency degradation point, connection exhaustion count, error rate curve). This makes the finding reproducible.

Further reading

Nyxeara perspective

Nyxeara's scanner approaches DoS detection by measuring resource asymmetry rather than simulating full-scale attacks. It identifies endpoints where the server-side cost (CPU time, memory allocation, I/O operations) significantly exceeds the client-side cost of a single request, and flags those exceeding a configurable threshold. For application-layer DoS, the scanner sends incremental request volumes to a staging endpoint and reports the sustained request rate at which latency degrades — providing a concrete capacity ceiling. Slow loris and ReDoS vectors are tested against the application's own configuration (connection timeouts, regex patterns) using minimal connections, rendering them detectable without saturating any shared resource.

Published 2026-09-16 · Updated 2026-09-16