Tools · Network tools · 16 / 24
Zeek: what a defender reads in http.log
http.log is one row per cleartext HTTP request, with the method, host, URI, status, and user agent. A populated password column, or a secret in the URI, means the lab still has a cleartext hop.
What one row already joined
Zeek puts the request and the response it paired on one line: method, host, uri, status_code, user_agent, request_body_len, response_body_len, and the uid. id.orig_h is the client. id.resp_h is the server. That is why http.log is faster than reconstructing the stream for a question about method and status.
The log exists for cleartext HTTP, or for HTTP that some lab device already turned back into cleartext before Zeek saw it. A capture of ordinary TLS has an ssl.log handshake and an empty http.log. If you expected a status code and the log is empty, go to the server, or to the TLS metadata, instead of assuming Zeek missed a path.
Columns a defender actually checks
Look for methods the service should not serve, hosts outside the lab's names, and URIs that carry tokens. user_agent is a hint about which program spoke. A rare agent is a question for the owner, not a verdict. status_code 401 and then 200 on the same client can be a lab login. If the username or password column is filled in, the secret crossed the network in the clear. Those columns exist so you can see that a secret was present. Leave the value out of the ticket.
resp_mime_types and the file id in resp_fuids point at files.log when Zeek extracted a body. Hash and store that body under the case rules. Do not open an extracted file on the analysis host. A MIME type you did not expect, such as an executable type on a service that should return text, is enough to escalate.
- host comes from the Host header. It can disagree with the address in id.resp_h.
- uri is the path and query. A query string with a token is the defect.
- request_body_len and response_body_len are sizes, which often answer the question.
- trans_depth greater than 1 means more than one request on the connection. The uid is still the join key.
A worked defensive case
http.log on the lab status capture shows GET /health from 10.8.0.20 to 10.8.0.40, status 200, user agent lab-health/1.0, and a small response. A second row is POST /lab/report?token=redacted, status 200, request_body_len 42, from the same client. The password column is empty. The token is in the URI.
You file a cleartext secret in a query string, cite the uid and the timestamp, and leave the token value out. The owner rotates it and moves the report off plain HTTP. /health can stay, because it carries no secret. The 443 capture from the other ticket stays a separate note with an empty http.log.
Where this is documented
HTTP::Info defines method, host, uri, status_code, user_agent, and the body lengths. Zeek's http.log page walks one row and shows the matching uid in conn.log.
Use a host-only lab network, or a capture the owner already handed you. These commands read that file or record a single lab host. They are not a scan of the public internet.
Commands
zeek-cut -m ts uid id.orig_h id.resp_h method host uri status_code user_agent request_body_len response_body_len < /lab/zeek/http.log
jq -c 'select(.method == "POST") | {ts, uid, orig: ."id.orig_h", host, uri, status_code, request_body_len}' /lab/zeek-json/http.logDiagram
The request Zeek collapsed into one http.log row
Cleartext lab HTTP, secret kept out of the note
http.log · POST · files.lab.internal
POST /lab/report?token=redacted HTTP/1.1 Host: files.lab.internal User-Agent: lab-health/1.0 Content-Length: 42
HTTP/1.1 200 OK Content-Type: text/csv Content-Length: 18
- One http.log row holds the method, host, URI, status, and both lengths.
- The token in the URI is the finding. The ticket omits the value.
- The uid on that row is the conn.log connection to quote.
http.log · GET · files.lab.internal
GET /health HTTP/1.1 Host: files.lab.internal User-Agent: lab-health/1.0
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 2
- A health check with no credential can stay on the lab port.
- status_code 200 and a short response_body_len match the row.
- This row is not the incident. The POST beside it is.
Check yourself
1. conn.log shows service ssl for 10.8.0.20 to 10.8.0.40:443, and http.log has no row for that uid. What does that combination mean?
2. A row's uri is /lab/report?token=redacted and status_code is 200. What is the change?
3. user_agent is a string the lab has never seen, on a single workstation. What is the proportionate note?