Zeek conn.log¶
한 줄 요약
연결(TCP, UDP, ICMP)마다 한 줄씩, 누가/무엇을/언제/얼마나와 TCP 핸드셰이크가 어떻게 진행됐는지 간단한 기록을 담습니다 — 가장 먼저 집계하는 로그이자 다른 모든 Zeek 로그가 매달린 uid 허브입니다.
필드¶
| 필드 | 타입 | 의미 | 분석 메모 |
|---|---|---|---|
ts |
time | 첫 패킷 시각 | UTC epoch. zeek-cut -d로 변환 |
uid |
string | 고유 연결 ID | 다른 모든 로그와의 결합 키 |
id.orig_h / id.orig_p |
addr / port | 시작 측(클라이언트) IP / 포트 | 시작 측 = 첫 패킷을 보낸 쪽 |
id.resp_h / id.resp_p |
addr / port | 응답 측(서버) IP / 포트 | 이걸로 정렬/집계해서 C2를 찾음 |
proto |
enum | tcp, udp, icmp |
|
service |
string | Zeek이 내용으로 탐지한 프로토콜 (http, ssl, dns, smb, ssh, rdp, krb_tcp, ntlm, dce_rpc…) |
- = 알 수 없음/탐지 안 됨. 여러 개: ssl,http. 포트와 안 맞으면 = 흥미로움 |
duration |
interval | 첫 패킷부터 마지막 패킷까지 초 | 아주 길고 바이트가 적으면 대화형/keep-alive |
orig_bytes / resp_bytes |
count | 시작 측 / 응답 측 페이로드 바이트 (TCP: 시퀀스 번호 기반이라 재전송으로 부풀지 않음) | 비대칭이 데이터 흐름 방향을 알려 줌 |
conn_state |
string | 연결 결과 요약 (아래 표) | 스캔: S0, REJ; 정상: SF |
local_orig / local_resp |
bool | orig/resp가 Site::local_nets에 속하나? |
로컬 네트워크를 설정했을 때만 채워짐 |
missed_bytes |
count | 패킷 드롭으로 잃은 바이트 | 높으면 = 캡처 문제, 바이트 수를 믿지 말 것 |
history |
string | 패킷별 플래그 기록 (아래) | 핸드셰이크 이야기처럼 읽힘 |
orig_pkts / orig_ip_bytes |
count | 시작 측의 패킷 수 / IP 수준 바이트 (헤더 포함) | orig_bytes와 비교 |
resp_pkts / resp_ip_bytes |
count | 응답 측도 동일 | |
tunnel_parents |
set | 감싸고 있는 터널의 uid (GRE, Teredo, VXLAN, AYIYA) |
비어 있지 않으면 = 터널링된 트래픽 |
community_id (로드했다면) |
string | Suricata/Arkime/Elastic과 공유하는 5-튜플 해시 | 도구 간 피벗 |
orig_l2_addr / resp_l2_addr (선택 정책) |
string | MAC 주소 | 로컬 구간에서 IP → 장치 연결 |
vlan, inner_vlan (선택) |
int | 802.1Q 태그 |
conn_state 값¶
| 상태 | 의미 | 보통 무엇인가 |
|---|---|---|
S0 |
SYN만 보이고 응답 없음 | 포트 스캔, 방화벽 드롭, 죽은 호스트 |
S1 |
핸드셰이크 완료, 종료 안 됨 | 캡처가 끝날 때 아직 열려 있음 / 긴 세션 |
SF |
정상 연결과 종료 | 일반 연결 ("Success Finished") |
REJ |
SYN → RST | 닫힌 포트 (스캔이 닫힌 포트에 닿음) |
S2 |
연결됨, orig가 FIN 보냄, resp의 FIN 없음 | |
S3 |
연결됨, resp가 FIN 보냄, orig의 FIN 없음 | |
RSTO |
연결됨, 시작 측이 RST로 중단 | 클라이언트가 끊음 |
RSTR |
연결됨, 응답 측이 RST로 중단 | 서버가 끊음 (IDS/방화벽 리셋, 서비스 크래시) |
RSTOS0 |
orig가 SYN 후 RST; resp의 SYN-ACK 없음 | 응답이 없자 RST를 보내는 스캐너 |
RSTRH |
resp가 SYN-ACK 후 RST; orig의 SYN은 안 보임 | 비대칭 캡처 |
SH |
orig SYN 후 FIN, SYN-ACK 없음 | "하프 오픈" — 스캔/회피인 경우가 많음 |
SHR |
resp SYN-ACK 후 FIN, orig의 SYN 없음 | 비대칭 캡처 |
OTH |
SYN 안 보임, 중간부터 | 연결 중간에 캡처 시작, 또는 UDP/ICMP |
history 글자¶
대문자 = 시작 측, 소문자 = 응답 측. 순서 = 관찰된 순서 (최신 버전은 반복을 합치고 ^로 방향 전환을 표시).
| 글자 | 의미 |
|---|---|
S / s |
SYN (ACK 없음) |
H / h |
SYN-ACK ("handshake") |
A / a |
순수 ACK |
D / d |
페이로드가 있는 패킷 ("data") |
F / f |
FIN |
R / r |
RST |
C / c |
잘못된 체크섬 |
G / g |
내용 공백 |
T / t |
재전송 |
W / w |
제로 윈도우 |
I / i |
모순된 패킷 (SYN+RST) |
Q / q |
다중 플래그 패킷 (SYN+FIN) |
^ |
방향 뒤집힘 (Zeek이 orig/resp를 잘못 추측했다가 고침) |
예시: ShADadFf = 완벽한 정상 연결. S = SYN 하나, 응답 없음 (스캔 / 드롭). Sr = SYN, RST 응답 (닫힌 포트). ShAD 후 아무것도 없음 = 클라이언트가 데이터를 보냈는데 아무것도 못 받았거나, 캡처가 끝났거나 멈춤. ShADadR = 서버가 리셋하기 전까지 정상. ^d = 응답 측 데이터만 보임, 중간부터.
빠른 쿼리¶
# Top talkers by destination (who is everyone connecting to?)
zeek-cut id.resp_h id.resp_p < conn.log | sort | uniq -c | sort -rn | head -20
# Outbound connections from one host, readable time, longest first
zeek-cut -d ts id.orig_h id.resp_h id.resp_p service duration orig_bytes resp_bytes conn_state < conn.log \
| awk '$2=="10.0.0.25"' | sort -k6 -rn | head
# Port scan detection: many S0/REJ from one source to many ports
zeek-cut id.orig_h id.resp_h id.resp_p conn_state < conn.log \
| awk '$4=="S0"||$4=="REJ"' | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head
# Services that don't match their port (443 that isn't ssl, 53 that isn't dns, 80 that isn't http)
zeek-cut id.resp_p service < conn.log | awk '($1==443 && $2!="ssl" && $2!="-") || ($1==53 && $2!="dns") || ($1==80 && $2!="http")' | sort | uniq -c | sort -rn
# Total bytes uploaded per internal host to external hosts (exfil hunt)
zeek-cut id.orig_h id.resp_h orig_bytes < conn.log | awk '$1 ~ /^10\./ && $2 !~ /^10\./ {s[$1" -> "$2]+=$3} END {for (k in s) print s[k], k}' | sort -rn | head
# Long-lived connections (> 1 hour)
zeek-cut -d ts uid id.orig_h id.resp_h id.resp_p duration < conn.log | awk '$6>3600' | sort -k6 -rn
index=zeek sourcetype=zeek:conn
| stats count sum(orig_bytes) as up sum(resp_bytes) as down avg(duration) as avg_dur by id.orig_h, id.resp_h, id.resp_p, service
| eval up_MB=round(up/1024/1024,1), down_MB=round(down/1024/1024,1)
| sort - up_MB
stats … by는 by 뒤의 필드로 행을 묶고 묶음마다 요약 행 하나를 계산합니다. sum(orig_bytes) as up은 업로드 바이트를 더해서 결과 이름을 up으로 붙입니다. eval은 수식으로 새 필드를 만듭니다(여기서는 바이트를 MB로 바꾸고 소수 첫째 자리로 반올림). sort - up_MB는 가장 많이 올린 쪽을 맨 위로 올립니다.
index=zeek sourcetype=zeek:conn conn_state IN (S0, REJ)
| stats dc(id.resp_p) as ports dc(id.resp_h) as hosts by id.orig_h
| where ports > 50 OR hosts > 50
IN (…)은 나열된 값 중 하나와 일치합니다. dc() = 고유 개수 — 한 출발지가 서로 다른 포트/호스트를 몇 개 건드렸는지; where는 임계값을 넘는 행만 남깁니다. 포트 스캔(ports)과 호스트 스윕(hosts) 탐지기입니다.
분석 팁¶
피벗 패턴
conn.log에서 집계 → 이상한 쌍(id.orig_h, id.resp_h, id.resp_p) 찾기 → 그 uid들 확보 → grep <uid> *.log → 프로토콜 상세 읽기. grep CHhAvVGS1DHFjwGM9 http.log ssl.log dns.log files.log.
- 바이트 수는 페이로드만입니다. 0바이트
SF연결은 데이터 없는 핸드셰이크 — TCP 프로브, 헬스 체크, 또는 할 일이 없었던 비콘입니다. orig_bytesvsorig_ip_bytes:orig_bytes는 0인데orig_ip_bytes가 크다면 페이로드가 재전송에 있었거나 Zeek이 재조립하지 못한 것(missed_bytes) — 캡처 품질 문제입니다.- UDP "연결"은 5-튜플로 묶고 비활동 타임아웃을 둔 플로우입니다.
duration이 여러 데이터그램에 걸칠 수 있고, 같은 리졸버로의 DNS는 긴 UDP 연결 하나로 보일 수 있습니다. - ICMP:
id.orig_p= ICMP 타입,id.resp_p= 코드.orig_bytes가 큰 타입 8/0이 많으면 = ICMP 터널. - 내부 vs. 외부:
local.zeek에Site::local_nets를 설정하면local_orig/local_resp가 채워집니다 — 그러면 "내부 → 외부,orig_bytes큼"이 한 줄짜리 쿼리가 됩니다. - 비코닝은 여기 있습니다: 같은
id.orig_h/id.resp_h/id.resp_p,ts값 사이의 거의 일정한 간격, 비슷한orig_bytes. 비코닝과 C2 참고. - 횡적 이동은 내부 → 내부
445(smb),135+ 높은 포트(dce_rpc),3389(rdp),5985(그 포트의http= WinRM),22로 보입니다. 사고 시간대 이전에는 한 번도 없던 내부 간 쌍을 세어 보세요. - Zeek이 orig/resp를 뒤집을 수 있습니다 — 중간부터 시작했을 때(history의
^,OTH상태). "서버"가 51234 포트라면 포트 번호를 다시 확인하세요.
상관분석¶
| 질문 | 다음에 볼 곳 |
|---|---|
| 실제로 무슨 내용이 오갔나? | uid → http.log, ssl.log, dns.log, smb_*.log, ssh.log, rdp.log |
| 어떤 파일이 오갔나? | uid → files.log (conn_uids) → fuid → pe.log, extract_files/ |
| 호스트의 어떤 프로세스가 만들었나? | Sysmon 3 (SourceIp/SourcePort/DestinationIp + 시각), Windows 5156, SRUM 앱별 시간당 바이트 |
| 어떤 사용자? | ntlm.log / kerberos.log (username, client), 목적지의 Windows 4624 |
| 경보가 떴나? | notice.log, Suricata eve.json (community_id / 5-튜플 + 시각) |
| 패킷 | tshark -r cap.pcap -Y "ip.addr==A && tcp.port==P" |