Detection rules › Splunk

HTTP Request to Reserved Name on IIS Server

Status
production
Severity
medium
Group by
Web.src, Web.status, Web.url_domain, c-uri, c-useragent, cs-host, cs-method
Author
Raven Tait, Splunk
Source
github.com/splunk/security_content

Detects attempts to exploit a request smuggling technique against IIS that leverages a Windows quirk where requests for reserved Windows device names such as "/con" trigger an early server response before the request body is received. When combined with a Content-Length desynchronization, this behavior can lead to a parsing confusion between frontend and backend.

MITRE ATT&CK coverage

Rule body splunk

name: HTTP Request to Reserved Name on IIS Server
id: 1e45e6a8-110b-4886-b815-8d69cf35bf0a
version: 6
creation_date: '2025-10-21'
modification_date: '2026-05-13'
author: Raven Tait, Splunk
status: production
type: TTP
description: |-
    Detects attempts to exploit a request smuggling technique against IIS that leverages a Windows quirk where requests for reserved Windows device names such as "/con" trigger an early server response before the request body is received.
    When combined with a Content-Length desynchronization, this behavior can lead to a parsing confusion between frontend and backend.
data_source:
    - Suricata
search: |-
    | tstats `security_content_summariesonly`
      count min(_time) as firstTime
            max(_time) as lastTime
    
    FROM datamodel=Web WHERE
    
    Web.url IN (
        "*/aux",
        "*/com1",
        "*/com2",
        "*/com3",
        "*/com4",
        "*/com5",
        "*/com6",
        "*/com7",
        "*/con",
        "*/nul",
        "*/prn"
    )
    
    BY Web.src Web.dest Web.http_user_agent
       Web.url Web.url_domain Web.status Web.http_method
    
    | `drop_dm_object_name("Web")`
    ```
    We have to add the logic below because the TA does not extract the URI path from the URL, and the anchors are short. Hence to avoid false positives, we need to extract the URI path from the URL and check if it is a reserved name.
    ```
    | eval uri=replace(url, url_domain, "")
    | where uri IN (
        "/aux",
        "/com1",
        "/com2",
        "/com3",
        "/com4",
        "/com5",
        "/com6",
        "/com7",
        "/con",
        "/nul",
        "/prn"
    )
    | `security_content_ctime(firstTime)`
    | `security_content_ctime(lastTime)`
    | `http_request_to_reserved_name_on_iis_server_filter`
how_to_implement: |-
    To implement this analytic, ensure proper logging is occurring with IIS, Apache, or a Proxy server and that these logs are being ingested into Splunk. The analytic was written against Suricata. The proper TA will need to be enabled and should be mapped to CIM and the Web datamodel. Ingestion of the data source is required to utilize this detection. In addition, if it is not mapped to the datamodel, modify the query for your application logs to look for requests the same URI and investigate further.
known_false_positives: |-
    False positives are not expected on IIS servers, as the detection is based on the presence of web requests to reserved names, which is not a common page to be accessed by legitimate users.
    Modify the query as needed to reduce false positives or hunt for additional indicators of compromise.
references:
    - https://portswigger.net/web-security/request-smuggling#what-is-http-request-smuggling
    - https://portswigger.net/research/http1-must-die
    - https://www.vaadata.com/blog/what-is-http-request-smuggling-exploitations-and-security-best-practices/
    - https://www.securityweek.com/new-http-request-smuggling-attacks-impacted-cdns-major-orgs-millions-of-websites/
drilldown_searches:
    - name: View the detection results for - "$dest$"
      search: '%original_detection_search% | search  dest = "$dest$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$dest$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$dest$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
finding:
    title: Known scripting tool was used against a web request. The source IP is $src$ and the destination is $dest$.
    entity:
        field: dest
        type: system
        score: 50
threat_objects:
    - field: src
      type: ip_address
analytic_story:
    - HTTP Request Smuggling
asset_type: Network
mitre_attack_id:
    - T1071.001
    - T1190
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: web
security_domain: network
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1190/request_smuggling/suricata_reserved_names.log
          sourcetype: suricata
          source: not_applicable
      test_type: unit

Stages and Predicates

Stage 1: tstats

| tstats `security_content_summariesonly`
  count min(_time) as firstTime
        max(_time) as lastTime

FROM datamodel=Web WHERE

Web.url IN (
    "*/aux",
    "*/com1",
    "*/com2",
    "*/com3",
    "*/com4",
    "*/com5",
    "*/com6",
    "*/com7",
    "*/con",
    "*/nul",
    "*/prn"
)

BY Web.src Web.dest Web.http_user_agent
   Web.url Web.url_domain Web.status Web.http_method

Stage 2: search

| `drop_dm_object_name("Web")`

Stage 3: eval

| eval uri=replace(url, url_domain, "")

Stage 4: where

| where uri IN (
    "/aux",
    "/com1",
    "/com2",
    "/com3",
    "/com4",
    "/com5",
    "/com6",
    "/com7",
    "/con",
    "/nul",
    "/prn"
)

Stage 5: search

| `security_content_ctime(firstTime)`

Stage 6: search

| `security_content_ctime(lastTime)`

Stage 7: search

| `http_request_to_reserved_name_on_iis_server_filter`

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
Web.urlin
  • "*/aux"
  • "*/com1"
  • "*/com2"
  • "*/com3"
  • "*/com4"
  • "*/com5"
  • "*/com6"
  • "*/com7"
  • "*/con"
  • "*/nul"
  • "*/prn"
uriin
  • "/aux"
  • "/com1"
  • "/com2"
  • "/com3"
  • "/com4"
  • "/com5"
  • "/com6"
  • "/com7"
  • "/con"
  • "/nul"
  • "/prn"