Detect Application Exhaustion Flood in Elastic Security
Adversaries may target resource-intensive features of web applications to cause a denial of service (DoS), denying availability to those applications. Unlike volumetric network-layer floods, application exhaustion attacks focus on Layer 7 features that consume disproportionate server resources per request — such as search functions, complex database queries, authentication endpoints, report generation, GraphQL resolvers, XML/SOAP processing, or file conversion operations. By repeatedly invoking these expensive operations, adversaries can exhaust CPU cycles, memory, database connection pools, or thread pools with relatively low request volumes, making the attack harder to distinguish from legitimate traffic spikes and more difficult to block at the network layer without application-aware controls.
MITRE ATT&CK
- Tactic
- Impact
- Technique
- T1499 Endpoint Denial of Service
- Sub-technique
- T1499.003 Application Exhaustion Flood
- Canonical reference
- https://attack.mitre.org/techniques/T1499/003/
Elastic Detection Query
sequence by source.ip, url.domain with maxspan=5m
[network where event.dataset in ("iis.access", "apache.access", "nginx.access") and
(url.path : ("*/search*", "*/query*", "*/find*", "*/report*", "*/export*", "*/download*",
"*/generate*", "*/convert*", "*/login*", "*/authenticate*", "*/auth*",
"*/oauth*", "*/signin*", "*/graphql*", "*/wp-login.php*", "*/xmlrpc.php*",
"*/wp-admin*", "*/rest/api*", "*/odata*", "*/api/*")
or http.response.body.bytes > 0)
] with runs=50
// Alternative aggregation-style query using stats:
// from logs-*
// | where event.dataset in ("iis.access", "apache.access", "nginx.access")
// | where @timestamp > now() - 1h
// | where url.path LIKE "%search%" or url.path LIKE "%graphql%" or url.path LIKE "%login%" or event.duration > 5000000000
// | stats
// request_count = count(),
// avg_duration_ms = avg(event.duration / 1000000),
// max_duration_ms = max(event.duration / 1000000),
// unique_endpoints = count_distinct(url.path),
// server_errors = sum(case(http.response.status_code >= 500, 1, 0)),
// rate_limited = sum(case(http.response.status_code == 429, 1, 0))
// by source.ip, url.domain, bucket(@timestamp, "5 minutes")
// | where request_count > 300 or (avg_duration_ms > 5000 and request_count > 50) or rate_limited > 10
from logs-*
| where event.dataset in ("iis.access", "apache.access", "nginx.access", "haproxy.log")
| where @timestamp > now() - 1h
| where (url.path like "*search*" or url.path like "*query*" or url.path like "*find*"
or url.path like "*report*" or url.path like "*export*" or url.path like "*download*"
or url.path like "*generate*" or url.path like "*convert*" or url.path like "*login*"
or url.path like "*authenticate*" or url.path like "*graphql*" or url.path like "*wp-login.php*"
or url.path like "*xmlrpc.php*" or url.path like "*wp-admin*" or url.path like "*rest/api*"
or url.path like "*odata*" or url.path like "*/api/*")
or (event.duration / 1000000) > 5000
| stats
request_count = count(),
avg_duration_ms = avg(event.duration / 1000000),
max_duration_ms = max(event.duration / 1000000),
unique_endpoints = count_distinct(url.path),
server_errors = count_if(http.response.status_code >= 500, 1),
rate_limited = count_if(http.response.status_code == 429, 1)
by source.ip, url.domain, bucket := date_trunc("5 minutes", @timestamp)
| where request_count > 300 or (avg_duration_ms > 5000 and request_count > 50) or rate_limited > 10
| eval threat_score = case(
request_count > 1000 and avg_duration_ms > 10000, 3,
request_count > 500 or avg_duration_ms > 8000, 2,
1)
| eval error_rate = round(server_errors / request_count, 2)
| sort threat_score desc, request_count desc Detects T1499.003 Application Exhaustion Flood by identifying single source IPs sending high volumes of requests to resource-intensive endpoints (search, GraphQL, auth, export, XML-RPC) or endpoints with elevated average response times indicating server resource exhaustion. Uses Elastic ECS web access log fields across IIS, Apache, and Nginx sources. Aggregates over 5-minute buckets and flags IPs exceeding 300 requests, averaging over 5 seconds response time with 50+ requests, or receiving 10+ rate-limit (HTTP 429) responses.
Data Sources
Required Tables
False Positives & Tuning
- Automated integration test suites or load testing frameworks (e.g., JMeter, k6, Locust) running against non-production environments may generate high request volumes to monitored endpoints
- Legitimate enterprise users running scheduled report generation or bulk data exports — particularly common during business hours for BI tools querying /report or /export endpoints
- Search engine crawlers (Googlebot, Bingbot) and security scanners that probe /search or /api endpoints at high frequency; these usually have well-known User-Agent strings that can be used to exclude them
- Internal monitoring agents performing health checks against /api/ endpoints at high frequency — typically identifiable by static source IPs or internal RFC1918 address ranges
Other platforms for T1499.003
Testing Methodology
Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Apache Bench Single-Source Application Endpoint Flood
Expected signal: W3CIISLog or Apache access.log: 5000 requests from 127.0.0.1 to /search endpoint within 30-60 seconds. User-Agent will show ApacheBench/2.X despite the override only applying to one header in some ab versions — check actual logs. TimeTaken values will show progressive degradation as server load increases. High RequestCount in 5-minute window from single source IP.
- Test 2Python Multi-threaded Concurrent Request Flood
Expected signal: Web access logs: 2000 requests from 127.0.0.1 to /api/search endpoint within 10-30 seconds with User-Agent 'python-requests/X.X.X'. High concurrency visible from overlapping request timestamps. If server load causes stress, HTTP 503 or 429 responses will appear in logs alongside 200s. ServerErrorCount or RateLimitedCount fields will be non-zero.
- Test 3curl Loop Targeting Authentication Endpoint with POST Bodies
Expected signal: Web access logs: 500 POST requests to /login from 127.0.0.1 with Content-Type: application/json. Average response time measurably higher than GET requests due to bcrypt cost. Application logs may show repeated authentication failure warnings. HTTP status codes will be 401 for invalid credentials or 429 if rate limiting activates.
- Test 4GraphQL Complexity Attack via Deeply Nested Query Flood
Expected signal: Web access logs: 200 POST requests to /graphql endpoint with deeply nested query payload. Response times significantly elevated (potentially >10s per request) due to N+1 resolver execution and recursive database queries. CPU utilization on application server and database server both spike. Database slow query logs will show high-volume repetitive queries triggered by the resolver chain.
References (8)
- https://attack.mitre.org/techniques/T1499/003/
- https://pages.arbornetworks.com/rs/082-KNA-087/images/13th_Worldwide_Infrastructure_Security_Report.pdf
- https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/netflow/configuration/15-mt/nf-15-mt-book/nf-detct-analy-thrts.pdf
- https://owasp.org/www-community/attacks/Denial_of_Service
- https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
- https://learn.microsoft.com/en-us/azure/web-application-firewall/overview
- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/w3ciislog
- https://learn.microsoft.com/en-us/iis/extensions/advanced-logging-module/advanced-logging-for-iis-real-time-logging
Unlock Pro Content
Get the full detection package for T1499.003 including response playbook, investigation guide, and atomic red team tests.