IDS/IPS ✓ fields verified against sample logs

Zeek Log Format (http.log): Field Reference and Examples

Published

One tab-separated line per transaction, written to a separate log per protocol. In http.log each line is one HTTP request and its response: who asked for which URI on which host, with what client, and what the server returned.

Sample record

#separator \x09
#set_separator	,
#empty_field	(empty)
#unset_field	-
#path	http
#open	2024-01-15-09-00-00
#fields	ts	uid	id.orig_h	id.orig_p	id.resp_h	id.resp_p	trans_depth	method	host	uri	referrer	version	user_agent	request_body_len	response_body_len	status_code	status_msg	info_code	info_msg	tags	username	password	proxied	orig_fuids	orig_filenames	orig_mime_types	resp_fuids	resp_filenames	resp_mime_types
#types	time	string	addr	port	addr	port	count	string	string	string	string	string	string	count	count	count	string	count	string	set[enum]	string	string	set[string]	vector[string]	vector[string]	vector[string]	vector[string]	vector[string]	vector[string]
1705312800.100000	CbCD23bcEFG	203.0.113.10	52100	10.0.0.50	80	1	GET	api.example.com	/api/v1/products	-	1.1	Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36	0	4096	200	OK	-	-	-	-	-	-	-	FbCD001	products.json	application/json	-	-	-

From the LogTriage parser test corpus — synthetic data, no real hosts or users.

Field reference

Field Type Meaning Why it matters for security
tse.g. 1705312800.100000 time (epoch seconds, fractional) When Zeek saw the request begin. Convert from epoch seconds before correlating with web server or firewall logs; the fractional part lets you order requests that arrive in the same second.
uide.g. CbCD23bcEFG string Zeek's unique identifier for the TCP connection the request travelled over. The pivot between logs — the same uid appears in conn.log, files.log, ssl.log and notice.log for that connection, so one suspicious request can be expanded into the whole session.
id.orig_he.g. 203.0.113.10 addr Address that originated the connection (the client). Group by this to find one source probing many paths; behind a proxy or load balancer it is the proxy, not the real client.
id.orig_pe.g. 52100 port Client source port. Many distinct source ports from one address in a short window means many separate connections, typical of scripted tooling.
id.resp_he.g. 10.0.0.50 addr Address that received the connection (the server). Identifies which of your hosts was targeted when one sensor covers several web servers.
id.resp_pe.g. 80 port Server port the request was sent to. HTTP seen on unexpected ports can indicate an admin interface or a service that was not supposed to be reachable.
methode.g. GET string HTTP request method. POST and PUT to authentication or upload endpoints are where credential stuffing and webshell drops appear.
hoste.g. api.example.com string Value of the Host header. Requests with an internal hostname, an IP literal or a host that does not belong to the server point at direct-to-IP scanning or vhost probing.
urie.g. /api/v1/products string Request URI, including the query string, as sent on the wire. The main detection surface — SQL keywords, traversal sequences and sensitive-file paths such as /.env or /admin/config.php show up here, URL-encoded.
referrere.g. https://app.example.com/dashboard string Value of the Referer header, if present. Real browser navigation usually carries a referrer; long runs of requests with no referrer suggest a crawler or a script.
user_agente.g. sqlmap/1.7.8#stable (https://sqlmap.org) string Value of the User-Agent header. Tool strings and generic HTTP libraries identify automation, but the header is attacker-controlled, so absence of a tool string proves nothing.
request_body_lene.g. 0 count Number of bytes of request body Zeek saw. Identical body lengths on repeated POSTs to a login endpoint indicate a loop submitting the same form shape with different credentials.
response_body_lene.g. 4096 count Number of bytes of response body Zeek saw. Compared across requests it separates rejected probes from responses that actually returned content.
status_codee.g. 200 count HTTP status code the server returned. A wall of 404s from one source is enumeration; a 200 on a path that should not exist, such as /.env, means something was served.
resp_mime_typese.g. application/json vector[string] MIME types of the response bodies, as identified by Zeek's file analysis rather than taken from the Content-Type header. text/plain or application/json returned for a config or credential path tells you data left the server; multiple values are joined with the set separator.

What it records

Zeek watches network traffic and writes one tab-separated line per transaction, split across per-protocol logs. In http.log each line is a single HTTP request and the response to it: the connection tuple (id.orig_h, id.orig_p, id.resp_h, id.resp_p), the method, host and URI, the client’s user agent and referrer, the sizes of the request and response bodies, the status code, and the MIME types Zeek’s file analysis identified in the response. trans_depth numbers the requests within one connection, so a keep-alive session produces several lines sharing a uid.

The file is self-describing. The #fields and #types lines name every column and its Zeek type, and #separator, #set_separator, #empty_field and #unset_field declare how values are encoded. #open and #close bracket the period the file covers.

Where to get it

On a sensor, Zeek writes logs under its install prefix (commonly /opt/zeek or /usr/local/zeek, depending on how it was built). Under ZeekControl the live files sit in logs/current and are rotated and compressed into dated directories, so a search across yesterday must read the archive, not only the current file. Running zeek -r capture.pcap over a packet capture produces the same set of logs in the working directory, which is the quickest way to generate http.log from evidence you were handed.

For shipping to a SIEM, Zeek can emit JSON instead of TSV (redef LogAscii::use_json = T, or one of the JSON streaming packages). Distributions that bundle Zeek, such as Security Onion, place the logs in their own directory layout.

Parsing pitfalls

  • Dashes are not data. A - means the field was unset and (empty) means an empty string, exactly as the header declares. Treat them as absence, not as values.
  • Read the header, not the position. Scripts and packages add columns, so column 10 is not always uri. Parse #fields for every file.
  • Multi-value columns. Types such as vector[string] and set[enum] — resp_mime_types, tags, orig_fuids — hold several values joined by the set separator, a comma by default.
  • Epoch timestamps. ts is seconds with a fractional part, not milliseconds; JSON output has its own timestamp setting, so the same field can arrive as a float or as a formatted string.
  • The URL is split. host and uri are separate columns; detections looking for a full URL have to concatenate them, and uri keeps the attacker’s URL encoding (%20 and friends) intact.
  • Field names contain dots. id.orig_h is a literal column name; in JSON output the key keeps the dot rather than becoming a nested object in every writer, so test your parser against real output.
  • Credentials. The username and password columns only ever cover HTTP basic authentication, and password capture is off unless it has been deliberately enabled.

Detection ideas

  • SQL injection probing: SQL keywords or encoded equivalents (UNION, SELECT, --) inside uri, often with a status_code of 200 and a tool string in user_agent.
  • Sensitive-file recon: requests for /.env, /admin/config.php, /wp-admin/install.php and similar paths — and treat any status_code 200 on them as an exposure, confirmed by resp_mime_types such as text/plain.
  • Credential stuffing: repeated POSTs to one authentication URI from a single id.orig_h, with identical request_body_len and a run of 401 responses.
  • Scripted clients: generic library user agents on paths a browser would reach, or a crawler user agent claimed by an address that does not belong to that crawler.
  • Enumeration: one id.orig_h touching many distinct uri values with mostly 404s in a few minutes.

These are the checks behind the Zeek HTTP web attack recon rule, and the browser log analyzer runs them locally on an http.log without uploading it.

Detections and guides for Zeek

Frequently Asked Questions

How do I get from one http.log line to the rest of the traffic?
Use the uid. Every Zeek log that describes the same connection carries the same uid, so grepping that value across conn.log, files.log, ssl.log and notice.log gives you byte counts, duration, extracted files and any notices for exactly that session.
Does Zeek record request and response bodies?
No. http.log stores only request_body_len and response_body_len plus the MIME types that file analysis identified. POST parameters and response content are not in the log unless you separately enable file extraction, so a login attempt shows its size but not the credentials used.
Why are some columns a single dash or the text (empty)?
Because the file tells you how it encodes missing data, and both markers mean something different. The header lines declare them — in the sample above
Can I rely on the column order staying the same?
Not across versions or deployments. Site scripts and packages can add or remove fields, and the order follows whatever is loaded, which is why every file declares its own

Analyze Zeek logs

LogTriage auto-detects this format, enriches every IP, user agent and hash with threat intelligence, and writes an incident report mapped to MITRE ATT&CK.