Skip to main content
Snort 3 includes a completely redesigned HTTP inspector written specifically for the new PDU-based inspection architecture. It handles HTTP/1.x natively and, together with http2_inspect, provides full HTTP/2 support.

Why a new HTTP inspector?

The classic Snort 2 HTTP preprocessor is always aware of IP packet boundaries even while parsing HTTP messages. This means the same HTTP message can be processed differently depending on how an attacker fragments it into packets — a significant source of evasion. http_inspect operates purely at the HTTP message level. It never sees raw packet boundaries. This makes it:
  • Simpler and less prone to false positives.
  • Immune to packet-boundary evasion.
  • Capable of true stateful processing — it can correlate a client request with its server response, and reason about multiple requests within a single session.
Every HTTP header field is normalized individually in the way that is semantically correct for that field, rather than treating all headers generically.

Basic configuration

Enable http_inspect with defaults by adding this to snort.lua:
http_inspect = { }
The default configuration performs thorough inspection and is suitable for most deployments. More targeted settings are described below.

Key configuration options

Inspection depth

By default, http_inspect inspects the entire message body. For environments with very large file transfers (videos, backups), limit body inspection depth:
http_inspect =
{
    request_depth  = 10000,   -- bytes of POST/PUT body to inspect
    response_depth = 80000,   -- bytes of response body to inspect
}
Set either depth to 0 to skip the body entirely. Set to -1 (or omit) to inspect the full body. Headers are always inspected regardless of depth settings.

Compression and encoding

OptionDefaultEffect
unziptrueDecompress gzip/deflate message bodies before inspection
normalize_utftrueDecode UTF-8/16/32 in response bodies based on Content-Type
decompress_pdffalseDecompress FlateDecode-filtered streams in PDF files
decompress_swffalseDecompress deflate/LZMA compressed SWF (Flash) files
decompress_zipfalseDecompress ZIP archives in message bodies
decompress_vbafalseDecompress RLE-compressed VBA macros in MS Office files

Allowed and disallowed methods

Control which HTTP methods are accepted. Alert 119:287 fires when a disallowed method is seen. Only one list can be configured at a time:
http_inspect =
{
    allowed_methods = "GET HEAD POST PUT DELETE OPTIONS",
    -- or:
    -- disallowed_methods = "TRACE CONNECT",
}

Script detection

Enable early forwarding of HTTP response bodies containing JavaScript to the detection engine as soon as the end of a <script> tag is found:
http_inspect = { script_detection = true }

Partial depth detection

Forward partial HTTP message data to the detection engine before the full message arrives, enabling faster threat detection:
http_inspect =
{
    partial_depth_body   = -1,   -- unlimited: enable for all partial bodies
    partial_depth_header = 4096, -- enable for partial headers up to 4096 bytes
}

URI normalization

The inspector normalizes percent-encoded URIs before making them available to rules. Key options:
http_inspect =
{
    utf8              = true,
    plus_to_space     = true,
    iis_double_decode = true,
    simplify_path     = true,
    backslash_to_slash = true,
    -- bad_characters = "0x00 0x25",
    -- percent_u = false,
}

X-Forwarded-For headers

Configure custom proxy headers that carry the original client IP (used by http_true_ip):
http_inspect =
{
    xff_headers = "x-forwarded-for true-client-ip x-real-ip",
}

Header limits

http_inspect =
{
    maximum_header_length      = 4096,  -- alert 119:19 when exceeded
    maximum_headers            = 200,   -- alert 119:20 when exceeded
    maximum_chunk_length       = 0,     -- 0 = no limit below 4 GB
    maximum_pipelined_requests = 99,    -- alert 119:34 when exceeded
    maximum_host_length        = -1,    -- -1 = disabled
}

HTTP rule buffers

http_inspect makes individual parts of every HTTP message available as rule buffers. Rules select a buffer and then apply content matches or other options to it.

Request buffers

BufferContents
http_uriNormalized URI (percent decoding, path simplification)
http_decoded_uriDecoded URI without path simplification
http_raw_uriUnmodified URI as it appeared on the wire
http_methodRequest method (GET, POST, PUT, etc.)
http_headerAll request or response headers (normalized)
http_raw_headerUnmodified header bytes
http_cookieValue of the Cookie header
http_raw_cookieUnmodified cookie value
http_client_bodyBody of POST/PUT requests
http_raw_bodyDechunked and unzipped body, not otherwise normalized
http_true_ipOriginal client IP from X-Forwarded-For or configured xff header
http_raw_requestUnmodified first line of the request
http_versionVersion string (HTTP/1.0, HTTP/1.1)

Response buffers

BufferContents
http_stat_codeThree-digit status code
http_stat_msgReason phrase after the status code
http_raw_statusUnmodified first line of the response
file_dataNormalized message body (decompressed, UTF-decoded)
js_dataNormalized JavaScript (requires js_norm module)
vba_dataDecompressed VBA macro data (requires decompress_zip + decompress_vba)

Trailer buffers

http_trailer and http_raw_trailer cover HTTP header lines that appear after a chunked body ends.

Range-based rule options

Some rule options check counts rather than content:
Rule optionWhat it checks
http_num_headersNumber of headers in the message
http_num_trailersNumber of trailer fields
http_num_cookiesNumber of cookies
http_max_header_lineLength of the longest header line
http_max_trailer_lineLength of the longest trailer line
http_version_matchHTTP version (1.0, 1.1, 2.0, 0.9, other, malformed)
http_header_testRange test or absence check on a specific header field
http_trailer_testRange test or absence check on a specific trailer field

Writing HTTP rules

URI inspection

# Match normalized URI (finds chocolate even if percent-encoded)
alert tcp any any -> any any (
    msg:"URI contains chocolate";
    flow:established, to_server;
    http_uri; content:"chocolate";
    sid:1; rev:1;
)

# Match raw (unnormalized) URI
alert tcp any any -> any any (
    msg:"Suspicious percent-encoding in URI";
    flow:established, to_server;
    http_raw_uri; content:"%63%68";
    sid:2; rev:1;
)

# Limit to the path component only
alert tcp any any -> any any (
    msg:"Traversal in path";
    flow:established, to_server;
    http_uri: path; content:"../";
    sid:3; rev:1;
)
The six URI components that can be specified individually are: path, query, fragment, host, port, scheme.

Header inspection

# Search all response headers (broad, less efficient)
alert tcp any any -> any any (
    msg:"Danish content-language";
    flow:established, to_client;
    http_header; content:"Content-Language: da", nocase;
    sid:4; rev:1;
)

# Search a specific header field (preferred: more efficient, not evaded by spacing)
alert tcp any any -> any any (
    msg:"Danish content-language";
    flow:established, to_client;
    http_header: field content-language; content:"da", nocase;
    sid:4; rev:2;
)

Method and status code

alert http (
    msg:"SQL Injection attempt";
    flow:established, to_server;
    http_method; content:"POST";
    http_uri; content:"union select", nocase;
    sid:5; rev:1;
)

alert http (
    msg:"Server error 500";
    flow:established, to_client;
    http_stat_code; content:"500";
    sid:6; rev:1;
)

Combining request and response

HTTP inspector is stateful. A single rule can match content in both the client request and the server response:
alert tcp any any -> any any (
    msg:"Response to suspicious URI request";
    flow:established, to_client;
    http_uri; content:"chocolate";
    file_data; content:"white chocolate";
    sid:7; rev:1;
)
This is a to_client rule. It alerts on the server response body (white chocolate) only when the client’s URI contained chocolate. The request sub-option makes the direction explicit when needed:
http_header: request; content:"chocolate";

Range-based header count

alert tcp any any -> any any (
    msg:"More than 100 headers";
    http_num_headers: > 100;
    sid:25; rev:1;
)

alert tcp any any -> any any (
    msg:"Header count between 100 and 200";
    http_num_headers: 100<>200;
    sid:26; rev:1;
)

HTTP/2 inspection

HTTP/2 is best understood as a framing layer that runs under HTTP/1.1 messages, not a replacement for HTTP/1.1 semantics. Key features it adds:
  • Multiplexing many requests over a single TCP connection.
  • HTTP header compression (HPACK).
  • Server push.
http2_inspect strips the HTTP/2 framing and feeds the resulting HTTP/1.1 messages to http_inspect. All HTTP rule buffers and rule options work identically on HTTP/2 traffic.

Configuration

http_inspect    = { }   -- required
http2_inspect   = { }   -- enables HTTP/2 support
Key options:
http2_inspect =
{
    concurrent_streams_limit = 100,      -- max simultaneous streams per flow (100–1000)
    settings_max_frame_size  = 16777215, -- max SETTINGS_MAX_FRAME_SIZE value
}

Service rule compatibility

Rules with service:http automatically apply to http2 flows as well:
alert tcp any any -> any any (
    flow:established, to_server;
    http_uri; content:"/admin";
    service: http;       -- also matches HTTP/2
    sid:10; rev:1;
)
To restrict a rule to HTTP/2 only:
service: http2;

Raw HTTP/2 frame inspection

For rules that need to inspect HTTP/2 framing directly:
alert http2 (
    msg:"SETTINGS frame with unusual max frame size";
    flow:to_server, established;
    http2_frame_header; content:"|04|", offset 3, depth 1;
    http2_frame_data;   content:"|00 05 12 34 56 78|";
    sid:11;
)
http2_frame_header covers the 9-byte HTTP/2 frame header. http2_frame_data covers the frame payload (after padding is removed). Both options must match the same frame within a rule.
Do not mix http2_frame_header or http2_frame_data with HTTP-level options (http_method, http_uri, etc.) in the same rule. The HTTP-level and frame-level buffers correspond to different HTTP/2 frames and cannot be correlated within a single rule evaluation.

JavaScript normalization

Snort 3 provides two JavaScript normalizers. The Enhanced Normalizer (js_norm) is the preferred choice for new rules.

Enhanced Normalizer (js_norm)

js_norm = { }
Capabilities:
  • Normalizes inline <script> blocks and external .js files.
  • Normalizes JavaScript embedded in PDF files (requires decompress_pdf = true on the relevant service inspector).
  • Stateful: tracks identifiers across multiple scripts in the same PDU.
  • Substitutes all non-ignored identifiers with unified names: var_0000 through var_ffff.
  • Expands unescape(), decodeURI(), String.fromCharCode() and similar obfuscation functions.
  • Supported by http_inspect, smtp, imap, and pop.
Key configuration options:
js_norm =
{
    bytes_depth      = -1,    -- -1 = unlimited; bytes of JS to normalize per script
    identifier_depth = 65536, -- max unique identifiers before alert 154:8
    max_tmpl_nest    = 32,    -- max template literal nesting depth
    max_bracket_depth = 256,  -- max parenthesis/brace/bracket nesting depth
    max_scope_depth  = 256,   -- max variable scope nesting depth
    ident_ignore     = { 'console', 'document', 'eval' },  -- keep these names unchanged
    prop_ignore      = { 'split', 'join', 'reverse' },     -- keep these properties unchanged
}
Or use the default configuration from snort_defaults.lua:
js_norm = default_js_norm

Writing rules with js_data

The js_data buffer contains the fully normalized JavaScript for the current PDU. It requires js_norm to be configured.
alert http ( msg:"Obfuscated script in HTTP"; js_data; content:"var var_0000"; sid:1; )
alert smtp ( msg:"Obfuscated script in email"; js_data; content:"var var_0000"; sid:2; )
The Enhanced Normalizer uses JIT processing. Normalization only happens when a rule with js_data is being evaluated. If the fast pattern of a rule does not match, JS normalization is skipped for that PDU. If js_data matches again later after a missed normalization, built-in alert 154:8 is raised.

Legacy Normalizer

The legacy normalizer is built into http_inspect and is deprecated. It normalizes unescape, String.fromCharCode, decodeURI, and decodeURIComponent calls and collapses consecutive whitespace:
http_inspect = { normalize_javascript = true }
Use the Enhanced Normalizer (js_norm) for new deployments.

Binder configuration for HTTP

The binder maps TCP connections on web ports to http_inspect, and optionally routes to http2_inspect based on protocol detection:
binder =
{
    {
        when = { proto = 'tcp', ports = '80 8080 8000 8443' },
        use  = { type = 'http_inspect' },
    },
    { use = { type = 'wizard' } },  -- wizard detects HTTP/2 via hex pattern
}

http_inspect  = { }
http2_inspect = { }
When the wizard detects an HTTP/2 connection (via the PRI * HTTP/2.0 preface), it routes the flow to http2_inspect, which then feeds into http_inspect.

CONNECT tunneling

The HTTP CONNECT method establishes a TCP tunnel through an HTTP proxy. When http_inspect receives a successful 2xx response to a CONNECT request, it stops processing the flow as HTTP and hands it back to the wizard to identify the tunneled protocol. If the tunneled protocol is HTTP/1.1, http_inspect resumes inspection as a new session.
If the client sends data after a CONNECT request before receiving the server response (pipeline traffic), the inspector will not hand off to the wizard, to prevent evasion through early-send tactics.