# [HIGH] Exploiting HTTP/2 CONTINUATION frames for DoS attacks

**Source:** Snyk
**Published:** 2024-04-08
**Article:** https://snyk.io/blog/exploiting-http-2-continuation-frames-dos-attacks/

## Threat Profile

Snyk Blog In this article
Written by Vandana Verma Sehgal 
April 8, 2024
0 mins read About the vulnerability The vulnerability lies in the way HTTP/2 implementations handle CONTINUATION frames, which are used to transmit header blocks larger than the maximum frame size. Attackers exploit this weakness by sending an excessive number of CONTINUATION frames within a single HTTP/2 stream. This flood of frames overwhelms the server's capacity to process them efficiently.
The severity of this vulnerab…

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2024-2653`
- **CVE:** `CVE-2024-27316`
- **CVE:** `CVE-2024-24549`
- **CVE:** `CVE-2024-31309`
- **CVE:** `CVE-2024-27919`
- **CVE:** `CVE-2024-30255`
- **CVE:** `CVE-2023-45288`
- **CVE:** `CVE-2024-28182`
- **CVE:** `CVE-2024-27983`
- **CVE:** `CVE-2024-2758`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1566.002** — Spearphishing Link
- **T1204.001** — User Execution: Malicious Link
- **T1059.001** — PowerShell
- **T1204.004** — User Execution: Malicious Copy and Paste
- **T1204.002** — User Execution: Malicious File
- **T1499.002** — Endpoint Denial of Service: Service Exhaustion Flood

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Vulnerable HTTP/2 server inventory: CONTINUATION flood CVE cluster (CVE-2024-27316 et al.)

`UC_1265_4` · phase: **recon** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Vulnerabilities.Vulnerabilities where Vulnerabilities.cve IN ("CVE-2024-27316","CVE-2024-24549","CVE-2024-31309","CVE-2024-27919","CVE-2024-30255","CVE-2023-45288","CVE-2024-28182","CVE-2024-27983","CVE-2024-2653","CVE-2024-2758") by Vulnerabilities.dest Vulnerabilities.cve Vulnerabilities.signature Vulnerabilities.severity Vulnerabilities.category | `drop_dm_object_name("Vulnerabilities")` | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | sort - severity
```

**Defender KQL:**
```kql
let HTTP2CveCluster = dynamic(["CVE-2024-27316","CVE-2024-24549","CVE-2024-31309","CVE-2024-27919","CVE-2024-30255","CVE-2023-45288","CVE-2024-28182","CVE-2024-27983","CVE-2024-2653","CVE-2024-2758"]);
let ExposedHosts = DeviceInfo
    | where Timestamp > ago(1d)
    | summarize arg_max(Timestamp, IsInternetFacing, PublicIP, OSPlatform) by DeviceId, DeviceName;
DeviceTvmSoftwareVulnerabilities
| where CveId in (HTTP2CveCluster)
| join kind=leftouter ExposedHosts on DeviceId
| project Timestamp, DeviceName, OSPlatform, IsInternetFacing, PublicIP,
          SoftwareVendor, SoftwareName, SoftwareVersion, CveId,
          VulnerabilitySeverityLevel, RecommendedSecurityUpdate
| order by IsInternetFacing desc, VulnerabilitySeverityLevel asc, DeviceName asc
```

### HTTP/2 server crash-loop on internet-facing host (CONTINUATION flood DoS exploitation signal)

`UC_1265_5` · phase: **actions** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Processes.process) as cmdSamples values(Processes.parent_process_name) as parents from datamodel=Endpoint.Processes where (Processes.process_name IN ("httpd","httpd.exe","nginx","nginx.exe","node","node.exe","java","java.exe","envoy","envoy.exe","traffic_server","traffic_manager") OR Processes.process IN ("*catalina*","*tomcat*","*bootstrap.jar*")) by Processes.dest Processes.process_name _time span=5m | `drop_dm_object_name("Processes")` | where count >= 3 | eval spanSec=lastTime-firstTime | where spanSec < 600 | sort - count
```

**Defender KQL:**
```kql
let WindowMin = 10m;
let RestartThreshold = 3;   // empirical: legit reload is 1-2 spawns; a flood-induced crash loop spikes to >=3 inside 10 min
let HTTP2Servers = dynamic(["httpd","httpd.exe","nginx","nginx.exe","node","node.exe","java","java.exe","envoy","envoy.exe","traffic_server","traffic_manager"]);
let Exposed = DeviceInfo
    | where Timestamp > ago(1d)
    | summarize arg_max(Timestamp, IsInternetFacing, PublicIP) by DeviceId, DeviceName
    | where IsInternetFacing == true;
DeviceProcessEvents
| where Timestamp > ago(1h)
| where FileName in~ (HTTP2Servers)
   or ProcessCommandLine has_any ("catalina", "org.apache.catalina", "tomcat", "trafficserver")
| where InitiatingProcessAccountName !endswith "$"
   and InitiatingProcessFileName !in~ ("apt", "apt-get", "yum", "dnf", "rpm", "dpkg", "systemctl", "service", "docker", "containerd-shim", "podman", "kubelet", "ansible", "chef-client", "puppet", "saltstack")
| where InitiatingProcessCommandLine !has "reload" and InitiatingProcessCommandLine !has "graceful" and InitiatingProcessCommandLine !has "--test"
| summarize Restarts = count(),
            FirstSeen = min(Timestamp),
            LastSeen = max(Timestamp),
            SampleCmd = any(ProcessCommandLine),
            Parents = make_set(InitiatingProcessFileName, 5)
            by DeviceId, DeviceName, FileName, bin(Timestamp, WindowMin)
| where Restarts >= RestartThreshold
| extend SpanSec = datetime_diff('second', LastSeen, FirstSeen)
| join kind=inner Exposed on DeviceId
| project FirstSeen, LastSeen, SpanSec, Restarts, DeviceName, PublicIP, FileName, Parents, SampleCmd
| order by Restarts desc
```

### Phishing-link click correlated to endpoint execution

`UC_PHISH_LINK` · phase: **delivery** · confidence: **High**

**Splunk SPL (CIM):**
```spl
``` Phishing-link click that drives endpoint execution within 60s ```
| tstats `summariesonly` earliest(_time) AS click_time
    from datamodel=Web
    where Web.action="allowed"
    by Web.src, Web.user, Web.dest, Web.url
| `drop_dm_object_name(Web)`
| rename user AS recipient, dest AS clicked_domain, url AS clicked_url
| join type=inner recipient
    [| tstats `summariesonly` count
         from datamodel=Email.All_Email
         where All_Email.action="delivered" AND All_Email.url!="-"
         by All_Email.recipient, All_Email.src_user, All_Email.url, All_Email.subject
     | `drop_dm_object_name(All_Email)`
     | rex field=url "https?://(?<email_domain>[^/]+)"
     | rename recipient AS recipient]
| join type=inner src
    [| tstats `summariesonly` earliest(_time) AS exec_time
         values(Processes.process) AS exec_cmd, values(Processes.process_name) AS exec_proc
         from datamodel=Endpoint.Processes
         where Processes.parent_process_name IN ("chrome.exe","msedge.exe","firefox.exe",
                                                   "outlook.exe","brave.exe","arc.exe")
           AND Processes.process_name IN ("powershell.exe","pwsh.exe","cmd.exe","mshta.exe",
                                            "rundll32.exe","regsvr32.exe","wscript.exe",
                                            "cscript.exe","bitsadmin.exe","certutil.exe",
                                            "curl.exe","wget.exe")
         by Processes.dest, Processes.user
     | `drop_dm_object_name(Processes)`
     | rename dest AS src]
| eval delta_sec = exec_time - click_time
| where delta_sec >= 0 AND delta_sec <= 60
| table click_time, exec_time, delta_sec, recipient, src, src_user, subject,
        clicked_domain, clicked_url, exec_proc, exec_cmd
| sort - click_time
```

**Defender KQL:**
```kql
// Phishing-link click that drives endpoint execution within 60s.
// Far higher fidelity than "every clicked URL" — most legitimate clicks
// never spawn a non-browser child process, so the join eliminates the
// 99% of noise that makes a raw click query unactionable.
let LookbackDays = 7d;
let SuspectClicks = UrlClickEvents
    | where Timestamp > ago(LookbackDays)
    | where AccountName !endswith "$"
    | where ActionType in ("ClickAllowed","ClickedThrough")
    | join kind=inner (
        EmailEvents
        | where Timestamp > ago(LookbackDays)
        | where DeliveryAction == "Delivered"
        | where EmailDirection == "Inbound"
        | project NetworkMessageId, Subject, SenderFromAddress, SenderFromDomain,
                  RecipientEmailAddress, EmailTimestamp = Timestamp
      ) on NetworkMessageId
    | join kind=leftouter (
        EmailUrlInfo | project NetworkMessageId, Url, UrlDomain
      ) on NetworkMessageId, Url
    | project ClickTime = Timestamp, AccountUpn, IPAddress, Url, UrlDomain,
              Subject, SenderFromAddress, SenderFromDomain, RecipientEmailAddress,
              ActionType;
// Correlate to a non-browser child process spawned within 60 seconds on
// the recipient's device.
DeviceProcessEvents
| where Timestamp > ago(LookbackDays)
| where InitiatingProcessFileName in~ ("chrome.exe","msedge.exe","firefox.exe",
                                         "outlook.exe","brave.exe","arc.exe")
| where FileName in~ ("powershell.exe","pwsh.exe","cmd.exe","mshta.exe",
                        "rundll32.exe","regsvr32.exe","wscript.exe","cscript.exe",
                        "bitsadmin.exe","certutil.exe","curl.exe","wget.exe")
| join kind=inner SuspectClicks on $left.AccountName == $right.AccountUpn
| where Timestamp between (ClickTime .. ClickTime + 60s)
| project ClickTime, ProcessTime = Timestamp,
          DelaySec = datetime_diff('second', Timestamp, ClickTime),
          DeviceName, AccountName, RecipientEmailAddress, SenderFromAddress,
          Subject, Url, UrlDomain, ActionType,
          FileName, ProcessCommandLine, InitiatingProcessFileName
| order by ClickTime desc
```

### Fake CAPTCHA / clipboard-injected PowerShell (ClickFix / FakeCaptcha)

`UC_FAKECAPTCHA` · phase: **exploit** · confidence: **High**

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime
    from datamodel=Endpoint.Processes
    where Processes.parent_process_name IN ("explorer.exe","RuntimeBroker.exe")
      AND Processes.process_name IN ("powershell.exe","pwsh.exe","mshta.exe")
      AND (Processes.process="*iex*" OR Processes.process="*Invoke-Expression*"
        OR Processes.process="*FromBase64*" OR Processes.process="*DownloadString*"
        OR Processes.process="*hxxp*" OR Processes.process="*curl*" OR Processes.process="*wget*")
    by Processes.dest, Processes.user, Processes.process, Processes.parent_process_name
| `drop_dm_object_name(Processes)`
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(7d)
| where AccountName !endswith "$"
| where InitiatingProcessFileName in~ ("explorer.exe","RuntimeBroker.exe")
| where FileName in~ ("powershell.exe","pwsh.exe","mshta.exe")
| where ProcessCommandLine matches regex @"(?i)(iex|invoke-expression|frombase64|downloadstring|hxxp|curl |wget )"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessCommandLine
```

### Article-specific behavioural hunt — Exploiting HTTP/2 CONTINUATION frames for DoS attacks

`UC_1265_3` · phase: **exploit** · confidence: **High**

**Splunk SPL (CIM):**
```spl
``` Article-specific bespoke detection — Exploiting HTTP/2 CONTINUATION frames for DoS attacks ```
| tstats `summariesonly` count earliest(_time) AS firstTime latest(_time) AS lastTime
    from datamodel=Endpoint.Processes
    where (Processes.process_name IN ("node.js"))
    by Processes.dest, Processes.user, Processes.process_name,
       Processes.process, Processes.parent_process_name, Processes.process_path
| `drop_dm_object_name(Processes)`
| `security_content_ctime(firstTime)`
| append [
| tstats `summariesonly` count
    from datamodel=Endpoint.Filesystem
    where Filesystem.action IN ("created","modified")
      AND (Filesystem.file_name IN ("node.js"))
    by Filesystem.dest, Filesystem.user, Filesystem.process_name,
       Filesystem.file_path, Filesystem.file_name
| `drop_dm_object_name(Filesystem)`
]
```

**Defender KQL:**
```kql
// Article-specific bespoke detection — Exploiting HTTP/2 CONTINUATION frames for DoS attacks
// Hunts the actual binaries / paths / commandline fragments named
// in the article instead of a generic technique-class template.
DeviceProcessEvents
| where Timestamp > ago(30d)
| where (FileName in~ ("node.js"))
| project Timestamp, DeviceName, AccountName, FileName,
          FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

// File-creation events for the named binaries / paths
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType in ("FileCreated","FileModified")
| where (FileName in~ ("node.js"))
| project Timestamp, DeviceName, AccountName, FolderPath,
          FileName, ActionType, InitiatingProcessFileName,
          InitiatingProcessCommandLine
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2024-2653`, `CVE-2024-27316`, `CVE-2024-24549`, `CVE-2024-31309`, `CVE-2024-27919`, `CVE-2024-30255`, `CVE-2023-45288`, `CVE-2024-28182` _(+2 more)_


## Why this matters

Severity classified as **HIGH** based on: CVE present, 6 use case(s) fired, 7 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
