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:
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
| Option | Default | Effect |
|---|
unzip | true | Decompress gzip/deflate message bodies before inspection |
normalize_utf | true | Decode UTF-8/16/32 in response bodies based on Content-Type |
decompress_pdf | false | Decompress FlateDecode-filtered streams in PDF files |
decompress_swf | false | Decompress deflate/LZMA compressed SWF (Flash) files |
decompress_zip | false | Decompress ZIP archives in message bodies |
decompress_vba | false | Decompress 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,
}
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",
}
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
| Buffer | Contents |
|---|
http_uri | Normalized URI (percent decoding, path simplification) |
http_decoded_uri | Decoded URI without path simplification |
http_raw_uri | Unmodified URI as it appeared on the wire |
http_method | Request method (GET, POST, PUT, etc.) |
http_header | All request or response headers (normalized) |
http_raw_header | Unmodified header bytes |
http_cookie | Value of the Cookie header |
http_raw_cookie | Unmodified cookie value |
http_client_body | Body of POST/PUT requests |
http_raw_body | Dechunked and unzipped body, not otherwise normalized |
http_true_ip | Original client IP from X-Forwarded-For or configured xff header |
http_raw_request | Unmodified first line of the request |
http_version | Version string (HTTP/1.0, HTTP/1.1) |
Response buffers
| Buffer | Contents |
|---|
http_stat_code | Three-digit status code |
http_stat_msg | Reason phrase after the status code |
http_raw_status | Unmodified first line of the response |
file_data | Normalized message body (decompressed, UTF-decoded) |
js_data | Normalized JavaScript (requires js_norm module) |
vba_data | Decompressed 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 option | What it checks |
|---|
http_num_headers | Number of headers in the message |
http_num_trailers | Number of trailer fields |
http_num_cookies | Number of cookies |
http_max_header_line | Length of the longest header line |
http_max_trailer_line | Length of the longest trailer line |
http_version_match | HTTP version (1.0, 1.1, 2.0, 0.9, other, malformed) |
http_header_test | Range test or absence check on a specific header field |
http_trailer_test | Range 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.
# 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";
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:
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)
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.