0x00 序言
网络安全技术是在攻防之对抗中发展的。不会攻击的防守者,可能无法猜测攻击方的意图和未来行动;不会防御的攻击者,亦很难猜测漏洞点、绕过各种 waf 和防守策略。于是,网络攻防即是攻心之战,能拟合对手思路的一方,便能在信息量较少的情况下预测对手的行为,从而占据上风。
本站在过去数年间,讨论过大量攻击技术;现在也该转换视角,从防守者一方重新认识网络安全。防守者最重要的工具便是 IDS——网络侧的 NIDS,以及主机侧的 HIDS。典型的 NIDS 会监听网络中的流量,利用特征库识别攻击事件,并报告给值守员。例如,假设我们关注 CVE-2023-1389(TP-Link 某款路由器的 RCE 漏洞),它的攻击报文是:
POST /cgi-bin/luci/;stok=/locale?form=country HTTP/1.1
Host: <target router>
Content-Type: application/x-www-form-urlencoded
operation=write&country=$(id>/tmp/out)
那么,我们便可能写出这样一条规则:“如果 url path 等于 /cgi-bin/luci/;stok=/locale?form=country
,则认为是针对 CVE-2023-1389 的攻击”。日后,当攻击者在网络中发送这种报文时,NIDS 产生警报,值守员便可追踪攻击者 IP,进行封堵和溯源工作。
固然,基于规则的 NIDS 对 0day 漏洞是无能为力的。然而,攻击者不太可能在攻击的全程都使用 0day 漏洞,因为攻击者缺乏内网资产信息。想要横向渗透,扫描几乎是不可避免的;一些典型的攻击尝试(例如 SQL 注入)会被 NIDS 发现;木马程序回连 C2 服务器的报文可能会被抓住;攻击者的提权行为可能会被 HIDS 记录下来。因此,我们事实上无需指望 NIDS 发现 0day 漏洞。在足够复杂的渗透过程中,攻击者总会留下踪迹,只要任何一点踪迹被抓住,防守方便获得了主动权。
目前,最流行的开源 NIDS 是 Snort 和 Suricata。在本系列连载文章中,我们将阅读 Suricata 的源码,了解其内部实现,并为二次开发做准备。今天我们先做最基础的工作——把 Suricata 运行起来。
0x01 搭建环境
笔者月初组装了一台 E5-2680v4 洋垃圾服务器,正好用于实验。在 ESXi 上摆出以下网络结构:
共有三台实验机,其中 suricata-host-1 与 suricata-host-2 进行日常通讯,由 suricata-lab 机器运行 IDS。三台机器都用静态 IP 接入实验网络 192.168.25.0/24
。
编辑 IDS 服务器的 /etc/network/interfaces
文件:
allow-hotplug ens224
iface ens224 inet static
address 192.168.25.10/24
执行 sudo systemctl restart networking
重启网络服务,即可接入实验网络。对 host1、host2 也执行相似步骤,最后给 IDS 服务器的网卡打开混杂模式:
ip link set ens224 promisc on
ip a
#3: ens224: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
# link/ether 00:0c:29:d9:c8:dd brd ff:ff:ff:ff:ff:ff
# altname enp19s0
# inet 192.168.25.10/24 brd 192.168.25.255 scope global ens224
# valid_lft forever preferred_lft forever
现在来测试 IDS 服务器是否可以抓包。在 host1 运行 HTTP 服务:
python -m http.server -b 0.0.0.0 8000
让 host2 定期访问此服务:
while true; do curl http://192.168.25.21:8000/ > /dev/null 2>&1; sleep 1 ; done
在 lab 机器用 tcpdump 抓包:
sudo tcpdump -i ens224 -c 100 -w out.pcap
抓包成功:
现在我们配完了服务,该开始部署 Suricata 了。
0x02 编译 Suricata
先把源码下载下来,看一眼代码规模:
proxychains wget 'https://www.openinfosecfoundation.org/download/suricata-7.0.8.tar.gz'
tar -zxvf suricata-7.0.8.tar.gz
cd suricata-7.0.8
cloc .
# 4903 text files.
# 4291 unique files.
# 841 files ignored.
#
# github.com/AlDanial/cloc v 1.96 T=6.19 s (693.6 files/s, 256749.6 lines/s)
# --------------------------------------------------------------------------------
# Language files blank comment code
# --------------------------------------------------------------------------------
# Rust 2122 59315 105324 700875
# C 706 62608 58508 319993
# Bourne Shell 27 9672 8832 57949
# C/C++ Header 658 13100 29041 40022
# Markdown 200 6791 58 19265
# reStructuredText 169 8232 2366 17979
# m4 14 1516 405 14561
# C++ 9 2903 3305 10695
# Text 16 2202 0 8755
# Python 62 1714 2102 7635
# TOML 129 837 1270 4967
# make 25 230 41 2626
# Perl 9 103 114 868
# YAML 11 85 244 533
# PHP 1 77 72 173
# JSON 119 0 0 137
# Bourne Again Shell 7 19 15 80
# SVG 1 0 0 34
# CSS 1 3 2 29
# Lua 1 11 10 27
# INI 2 4 0 22
# Dockerfile 1 6 0 19
# PowerShell 1 7 2 13
# --------------------------------------------------------------------------------
# SUM: 4291 169435 211711 1207257
# --------------------------------------------------------------------------------
据 cloc 工具统计,Suricata 共有 4291 个文件,1207257 行代码,算是一个大型项目。与之相比,sqlite3 只有 191532 行代码,AFL 仅 12179 行代码。完整阅读源码显然不现实,我们只能挑感兴趣的看。
编译并安装:
sudo apt install autoconf automake build-essential cargo cbindgen libjansson-dev libpcap-dev libpcre2-dev libtool libyaml-dev make pkg-config rustc zlib1g-dev
./configure
make -j16
du -h ./src/.libs/suricata
# 102M ./src/.libs/suricata
ldd ./src/.libs/suricata
# linux-vdso.so.1 (0x00007fff52594000)
# libhtp.so.2 => not found
# libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fbe06a88000)
# libjansson.so.4 => /lib/x86_64-linux-gnu/libjansson.so.4 (0x00007fbe06a78000)
# libyaml-0.so.2 => /lib/x86_64-linux-gnu/libyaml-0.so.2 (0x00007fbe05fdf000)
# libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fbe05f45000)
# libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fbe05f26000)
# libpcap.so.0.8 => /lib/x86_64-linux-gnu/libpcap.so.0.8 (0x00007fbe05eda000)
# libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fbe05eba000)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe05cd9000)
# /lib64/ld-linux-x86-64.so.2 (0x00007fbe06b71000)
# libdbus-1.so.3 => /lib/x86_64-linux-gnu/libdbus-1.so.3 (0x00007fbe05c83000)
# libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007fbe05bb3000)
# libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007fbe05ba7000)
# libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007fbe05a60000)
# liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fbe05a31000)
# libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007fbe05975000)
# liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007fbe0594f000)
# libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007fbe05927000)
sudo make install
sudo make install-conf
执行 suricata --build-info
,查看支持的功能:
This is Suricata version 7.0.8 RELEASE
Features: PCAP_SET_BUFF AF_PACKET HAVE_PACKET_FANOUT HAVE_HTP_URI_NORMALIZE_HOOK PCRE_JIT HAVE_NSS HTTP2_DECOMPRESSION HAVE_JA3 HAVE_JA4 HAVE_LIBJANSSON TLS TLS_C11 RUST POPCNT64
SIMD support: SSE_4_2 SSE_4_1 SSE_3 SSE_2
Atomic intrinsics: 1 2 4 8 16 byte(s)
64-bits, Little-endian architecture
GCC version 12.2.0, C version 201112
compiled with _FORTIFY_SOURCE=0
L1 cache line size (CLS)=64
thread local storage method: _Thread_local
compiled with LibHTP v0.5.49, linked against LibHTP v0.5.49
Suricata Configuration:
AF_PACKET support: yes
AF_XDP support: no
DPDK support: no
eBPF support: no
XDP support: no
PF_RING support: no
NFQueue support: no
NFLOG support: no
IPFW support: no
Netmap support: no
DAG enabled: no
Napatech enabled: no
WinDivert enabled: no
Unix socket enabled: yes
Detection enabled: yes
Libmagic support: no
libjansson support: yes
hiredis support: no
hiredis async with libevent: no
PCRE jit: yes
LUA support: no
libluajit: no
GeoIP2 support: no
JA3 support: yes
JA4 support: yes
Non-bundled htp: no
Hyperscan support: no
Libnet support: no
liblz4 support: no
Landlock support: yes
Rust support: yes
Rust strict mode: no
Rust compiler path: /usr/bin/rustc
Rust compiler version: rustc 1.63.0
Cargo path: /usr/bin/cargo
Cargo version: cargo 1.65.0
Python support: yes
Python path: /usr/bin/python3
Install suricatactl: yes
Install suricatasc: yes
Install suricata-update: yes
Profiling enabled: no
Profiling locks enabled: no
Profiling rules enabled: no
Plugin support (experimental): yes
DPDK Bond PMD: no
Development settings:
Coccinelle / spatch: no
Unit tests enabled: no
Debug output enabled: no
Debug validation enabled: no
Fuzz targets enabled: no
Generic build parameters:
Installation prefix: /usr/local
Configuration directory: /usr/local/etc/suricata/
Log directory: /usr/local/var/log/suricata/
--prefix /usr/local
--sysconfdir /usr/local/etc
--localstatedir /usr/local/var
--datarootdir /usr/local/share
Host: x86_64-pc-linux-gnu
Compiler: gcc (exec name) / g++ (real)
GCC Protect enabled: no
GCC march native enabled: yes
GCC Profile enabled: no
Position Independent Executable enabled: no
CFLAGS -g -O2 -fPIC -std=c11 -march=native -I${srcdir}/../rust/gen -I${srcdir}/../rust/dist
PCAP_CFLAGS -I/usr/include
SECCFLAGS
0x03 首次运行
按照官方文档指引,我们把 Suricata 运行起来,采用默认的 suricata.yaml
文件。
sudo suricata -c suricata.yaml -i ens224
# i: suricata: This is Suricata version 7.0.8 RELEASE running in SYSTEM mode
# W: detect: No rule files match the pattern /usr/local/var/lib/suricata/rules/suricata.rules
# W: detect: 1 rule files specified, but no rules were loaded!
# i: threads: Threads created -> W: 16 FM: 1 FR: 1 Engine started.
现在可以在 /usr/local/var/log/suricata/eve.json
看到信息:
{"timestamp":"2024-12-31T14:57:25.849738+0800","flow_id":1667686480329036,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55792,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:26.878913+0800","flow_id":1792877521196729,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55796,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:26.878989+0800","flow_id":1792877521196729,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55796,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:27.143629+0800","flow_id":724822544231338,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":40386,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:26.299832+0800","end":"2024-12-31T14:56:26.302813+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:27.904702+0800","flow_id":2186168948960977,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55800,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:27.905031+0800","flow_id":2186168948960977,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55800,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:28.138586+0800","flow_id":215858337435564,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":40362,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:24.246866+0800","end":"2024-12-31T14:56:24.249848+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:28.932648+0800","flow_id":54022912867887,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55804,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:28.932907+0800","flow_id":54022912867887,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55804,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:29.131677+0800","flow_id":1925890420143592,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":45726,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:22.186262+0800","end":"2024-12-31T14:56:22.189601+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:29.961290+0800","flow_id":459079160132775,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55810,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:29.961553+0800","flow_id":459079160132775,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55810,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:30.126511+0800","flow_id":1232570106215586,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":40408,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:28.352516+0800","end":"2024-12-31T14:56:28.355172+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:30.126596+0800","flow_id":849355708794600,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":40398,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:27.328828+0800","end":"2024-12-31T14:56:27.331637+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:30.986816+0800","flow_id":569585911662863,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55818,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:30.987070+0800","flow_id":569585911662863,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55818,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
{"timestamp":"2024-12-31T14:57:31.119378+0800","flow_id":328646049902938,"in_iface":"ens224","event_type":"flow","src_ip":"192.168.25.22","src_port":40378,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","app_proto":"http","flow":{"pkts_toserver":6,"pkts_toclient":5,"bytes_toserver":486,"bytes_toclient":1630,"start":"2024-12-31T14:56:25.273126+0800","end":"2024-12-31T14:56:25.276085+0800","age":0,"state":"closed","reason":"timeout","alerted":false},"tcp":{"tcp_flags":"1b","tcp_flags_ts":"1b","tcp_flags_tc":"1b","syn":true,"fin":true,"psh":true,"ack":true,"state":"closed","ts_max_regions":1,"tc_max_regions":1}}
{"timestamp":"2024-12-31T14:57:31.998449+0800","event_type":"stats","stats":{"uptime":1376,"capture":{"kernel_packets":14776,"kernel_drops":0,"errors":0,"afpacket":{"busy_loop_avg":0,"polls":229465,"poll_signal":0,"poll_timeout":218685,"poll_data":10780,"poll_errors":0,"send_errors":0}},"decoder":{"pkts":14774,"bytes":2834908,"invalid":0,"ipv4":14773,"ipv6":1,"ethernet":14774,"arp":0,"unknown_ethertype":0,"chdlc":0,"raw":0,"null":0,"sll":0,"tcp":14773,"udp":0,"sctp":0,"esp":0,"icmpv4":0,"icmpv6":1,"ppp":0,"pppoe":0,"geneve":0,"gre":0,"vlan":0,"vlan_qinq":0,"vlan_qinqinq":0,"vxlan":0,"vntag":0,"ieee8021ah":0,"teredo":0,"ipv4_in_ipv6":0,"ipv6_in_ipv6":0,"mpls":0,"avg_pkt_size":191,"max_pkt_size":1202,"max_mac_addrs_src":0,"max_mac_addrs_dst":0,"erspan":0,"nsh":0,"event":{"ipv4":{"pkt_too_small":0,"hlen_too_small":0,"iplen_smaller_than_hlen":0,"trunc_pkt":0,"opt_invalid":0,"opt_invalid_len":0,"opt_malformed":0,"opt_pad_required":0,"opt_eol_required":0,"opt_duplicate":0,"opt_unknown":0,"wrong_ip_version":0,"icmpv6":0,"frag_pkt_too_large":0,"frag_overlap":0,"frag_ignored":0},"icmpv4":{"pkt_too_small":0,"unknown_type":0,"unknown_code":0,"ipv4_trunc_pkt":0,"ipv4_unknown_ver":0},"icmpv6":{"unknown_type":0,"unknown_code":0,"pkt_too_small":0,"ipv6_unknown_version":0,"ipv6_trunc_pkt":0,"mld_message_with_invalid_hl":0,"unassigned_type":0,"experimentation_type":0},"ipv6":{"pkt_too_small":0,"trunc_pkt":0,"trunc_exthdr":0,"exthdr_dupl_fh":0,"exthdr_useless_fh":0,"exthdr_dupl_rh":0,"exthdr_dupl_hh":0,"exthdr_dupl_dh":0,"exthdr_dupl_ah":0,"exthdr_dupl_eh":0,"exthdr_invalid_optlen":0,"wrong_ip_version":0,"exthdr_ah_res_not_null":0,"hopopts_unknown_opt":0,"hopopts_only_padding":0,"dstopts_unknown_opt":0,"dstopts_only_padding":0,"rh_type_0":0,"zero_len_padn":0,"fh_non_zero_reserved_field":0,"data_after_none_header":0,"unknown_next_header":0,"icmpv4":0,"frag_pkt_too_large":0,"frag_overlap":0,"frag_invalid_length":0,"frag_ignored":0,"ipv4_in_ipv6_too_small":0,"ipv4_in_ipv6_wrong_version":0,"ipv6_in_ipv6_too_small":0,"ipv6_in_ipv6_wrong_version":0},"tcp":{"pkt_too_small":0,"hlen_too_small":0,"invalid_optlen":0,"opt_invalid_len":0,"opt_duplicate":0},"udp":{"pkt_too_small":0,"hlen_too_small":0,"hlen_invalid":0,"len_invalid":0},"sll":{"pkt_too_small":0},"ethernet":{"pkt_too_small":0},"ppp":{"pkt_too_small":0,"vju_pkt_too_small":0,"ip4_pkt_too_small":0,"ip6_pkt_too_small":0,"wrong_type":0,"unsup_proto":0},"pppoe":{"pkt_too_small":0,"wrong_code":0,"malformed_tags":0},"gre":{"pkt_too_small":0,"wrong_version":0,"version0_recur":0,"version0_flags":0,"version0_hdr_too_big":0,"version0_malformed_sre_hdr":0,"version1_chksum":0,"version1_route":0,"version1_ssr":0,"version1_recur":0,"version1_flags":0,"version1_no_key":0,"version1_wrong_protocol":0,"version1_malformed_sre_hdr":0,"version1_hdr_too_big":0},"vlan":{"header_too_small":0,"unknown_type":0,"too_many_layers":0},"ieee8021ah":{"header_too_small":0},"vntag":{"header_too_small":0,"unknown_type":0},"ipraw":{"invalid_ip_version":0},"ltnull":{"pkt_too_small":0,"unsupported_type":0},"sctp":{"pkt_too_small":0},"esp":{"pkt_too_small":0},"mpls":{"header_too_small":0,"pkt_too_small":0,"bad_label_router_alert":0,"bad_label_implicit_null":0,"bad_label_reserved":0,"unknown_payload_type":0},"vxlan":{"unknown_payload_type":0},"geneve":{"unknown_payload_type":0},"erspan":{"header_too_small":0,"unsupported_version":0,"too_many_vlan_layers":0},"dce":{"pkt_too_small":0},"chdlc":{"pkt_too_small":0},"nsh":{"header_too_small":0,"unsupported_version":0,"bad_header_length":0,"reserved_type":0,"unsupported_type":0,"unknown_payload":0}},"too_many_layers":0},"tcp":{"syn":1338,"synack":1338,"rst":0,"urg":0,"active_sessions":82,"sessions":1338,"ssn_memcap_drop":0,"ssn_from_cache":39,"ssn_from_pool":1299,"pseudo":0,"pseudo_failed":0,"invalid_checksum":0,"midstream_pickups":0,"pkt_on_wrong_thread":0,"ack_unseen_data":94,"segment_memcap_drop":0,"segment_from_cache":3918,"segment_from_pool":48,"stream_depth_reached":0,"reassembly_gap":0,"overlap":0,"overlap_diff_data":0,"insert_data_normal_fail":0,"insert_data_overlap_fail":0,"urgent_oob_data":0,"memuse":9961472,"reassembly_memuse":2166784},"flow":{"memcap":0,"total":1339,"active":82,"tcp":1338,"udp":0,"icmpv4":0,"icmpv6":1,"tcp_reuse":5,"get_used":0,"get_used_eval":0,"get_used_eval_reject":0,"get_used_eval_busy":0,"get_used_failed":0,"wrk":{"spare_sync_avg":100,"spare_sync":16,"spare_sync_incomplete":0,"spare_sync_empty":0,"flows_evicted_needs_work":35,"flows_evicted_pkt_inject":70,"flows_evicted":6,"flows_injected":33,"flows_injected_max":0},"end":{"state":{"new":1,"established":30,"closed":1226,"local_bypassed":0},"tcp_state":{"none":0,"syn_sent":0,"syn_recv":0,"established":0,"fin_wait1":0,"fin_wait2":0,"time_wait":5,"last_ack":0,"close_wait":30,"closing":0,"closed":1221},"tcp_liberal":0},"mgr":{"full_hash_pass":193,"rows_per_sec":9175,"rows_maxlen":2,"flows_checked":2760,"flows_notimeout":1509,"flows_timeout":1251,"flows_evicted":1251,"flows_evicted_needs_work":33},"spare":10817,"emerg_mode_entered":0,"emerg_mode_over":0,"recycler":{"recycled":1218,"queue_avg":0,"queue_max":4},"memuse":7509504},"defrag":{"ipv4":{"fragments":0,"reassembled":0},"ipv6":{"fragments":0,"reassembled":0},"max_frag_hits":0},"flow_bypassed":{"local_pkts":0,"local_bytes":0,"local_capture_pkts":0,"local_capture_bytes":0,"closed":0,"pkts":0,"bytes":0},"detect":{"engines":[{"id":0,"last_reload":"2024-12-31T14:34:35.454475+0800","rules_loaded":0,"rules_failed":0,"rules_skipped":0}],"alert":0,"alert_queue_overflow":0,"alerts_suppressed":0},"app_layer":{"flow":{"http":1338,"ftp":0,"smtp":0,"tls":0,"ssh":0,"imap":0,"smb":0,"dcerpc_tcp":0,"dns_tcp":0,"nfs_tcp":0,"ntp":0,"ftp-data":0,"tftp":0,"ike":0,"krb5_tcp":0,"quic":0,"dhcp":0,"snmp":0,"sip":0,"rfb":0,"mqtt":0,"telnet":0,"rdp":0,"http2":0,"bittorrent-dht":0,"failed_tcp":0,"dcerpc_udp":0,"dns_udp":0,"nfs_udp":0,"krb5_udp":0,"failed_udp":0},"tx":{"http":1338,"ftp":0,"smtp":0,"tls":0,"ssh":0,"imap":0,"smb":0,"dcerpc_tcp":0,"dns_tcp":0,"nfs_tcp":0,"ntp":0,"ftp-data":0,"tftp":0,"ike":0,"krb5_tcp":0,"quic":0,"dhcp":0,"snmp":0,"sip":0,"rfb":0,"mqtt":0,"telnet":0,"rdp":0,"http2":0,"bittorrent-dht":0,"dcerpc_udp":0,"dns_udp":0,"nfs_udp":0,"krb5_udp":0},"error":{"http":{"gap":0,"alloc":0,"parser":0,"internal":0},"ftp":{"gap":0,"alloc":0,"parser":0,"internal":0},"smtp":{"gap":0,"alloc":0,"parser":0,"internal":0},"tls":{"gap":0,"alloc":0,"parser":0,"internal":0},"ssh":{"gap":0,"alloc":0,"parser":0,"internal":0},"imap":{"gap":0,"alloc":0,"parser":0,"internal":0},"smb":{"gap":0,"alloc":0,"parser":0,"internal":0},"dcerpc_tcp":{"gap":0,"alloc":0,"parser":0,"internal":0},"dns_tcp":{"gap":0,"alloc":0,"parser":0,"internal":0},"nfs_tcp":{"gap":0,"alloc":0,"parser":0,"internal":0},"ntp":{"gap":0,"alloc":0,"parser":0,"internal":0},"ftp-data":{"gap":0,"alloc":0,"parser":0,"internal":0},"tftp":{"gap":0,"alloc":0,"parser":0,"internal":0},"ike":{"gap":0,"alloc":0,"parser":0,"internal":0},"krb5_tcp":{"gap":0,"alloc":0,"parser":0,"internal":0},"quic":{"gap":0,"alloc":0,"parser":0,"internal":0},"dhcp":{"gap":0,"alloc":0,"parser":0,"internal":0},"snmp":{"gap":0,"alloc":0,"parser":0,"internal":0},"sip":{"gap":0,"alloc":0,"parser":0,"internal":0},"rfb":{"gap":0,"alloc":0,"parser":0,"internal":0},"mqtt":{"gap":0,"alloc":0,"parser":0,"internal":0},"telnet":{"gap":0,"alloc":0,"parser":0,"internal":0},"rdp":{"gap":0,"alloc":0,"parser":0,"internal":0},"http2":{"gap":0,"alloc":0,"parser":0,"internal":0},"bittorrent-dht":{"gap":0,"alloc":0,"parser":0,"internal":0},"failed_tcp":{"gap":0},"dcerpc_udp":{"alloc":0,"parser":0,"internal":0},"dns_udp":{"alloc":0,"parser":0,"internal":0},"nfs_udp":{"alloc":0,"parser":0,"internal":0},"krb5_udp":{"alloc":0,"parser":0,"internal":0}},"expectations":0},"memcap_pressure":14,"memcap_pressure_max":14,"http":{"memuse":17972,"memcap":0},"ftp":{"memuse":0,"memcap":0},"file_store":{"open_files":0}}}
{"timestamp":"2024-12-31T14:57:32.012900+0800","flow_id":1170911115404055,"in_iface":"ens224","event_type":"http","src_ip":"192.168.25.22","src_port":55822,"dest_ip":"192.168.25.21","dest_port":8000,"proto":"TCP","pkt_src":"wire/pcap","tx_id":0,"http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136}}
{"timestamp":"2024-12-31T14:57:32.013155+0800","flow_id":1170911115404055,"in_iface":"ens224","event_type":"fileinfo","src_ip":"192.168.25.21","src_port":8000,"dest_ip":"192.168.25.22","dest_port":55822,"proto":"TCP","pkt_src":"wire/pcap","http":{"hostname":"192.168.25.21","http_port":8000,"url":"/","http_user_agent":"curl/7.88.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":1136},"app_proto":"http","fileinfo":{"filename":"/","gaps":false,"state":"CLOSED","stored":false,"size":1136,"tx_id":0}}
eve 文件中出现了几种 event_type
,分别是 fileinfo
、http
、flow
、stats
。其中,类型为 http
的 eve json 会包含一个 http
字段,内容大致是:
"http": {
"hostname": "192.168.25.21",
"http_port": 8000,
"url": "/",
"http_user_agent": "curl/7.88.1",
"http_content_type": "text/html",
"http_method": "GET",
"protocol": "HTTP/1.1",
"status": 200,
"length": 1136
},
根据文档,我们可以配置哪些字段需要记录,例如 cookie 等。而 fileinfo
是:
"fileinfo": {
"filename": "/",
"gaps": false,
"state": "CLOSED",
"stored": false,
"size": 1136,
"tx_id": 0
}
我们没有开启文件提取功能,所以 stored=false
。flow
是一个通用类型,内容:
"flow": {
"pkts_toserver": 6,
"pkts_toclient": 5,
"bytes_toserver": 486,
"bytes_toclient": 1630,
"start": "2024-12-31T14:56:28.352516+0800",
"end": "2024-12-31T14:56:28.355172+0800",
"age": 0,
"state": "closed",
"reason": "timeout",
"alerted": false
},
"tcp": {
"tcp_flags": "1b",
"tcp_flags_ts": "1b",
"tcp_flags_tc": "1b",
"syn": true,
"fin": true,
"psh": true,
"ack": true,
"state": "closed",
"ts_max_regions": 1,
"tc_max_regions": 1
}
最后是 stats
类型。这种 eve 是 Suricata 自身的状态日志,记录了 Suricata 运行时间、抓包计数、解码计数等信息。
"stats": {
"uptime": 1376,
"capture": {
"kernel_packets": 14776,
"kernel_drops": 0,
"errors": 0,
"afpacket": {
"busy_loop_avg": 0,
"polls": 229465,
"poll_signal": 0,
"poll_timeout": 218685,
"poll_data": 10780,
"poll_errors": 0,
"send_errors": 0
}
},
"decoder": {
"pkts": 14774,
"bytes": 2834908,
"invalid": 0,
"ipv4": 14773,
"ipv6": 1,
"ethernet": 14774,
"arp": 0,
// ......
于是,我们想要监测网络中的事件,只需观察 eve.json
的新增条目。接下来,我们配置一条告警规则。
0x04 配置告警规则 & 重放 pcap
编写 tplink.rules
文件:
alert http any any -> any any (msg:"CVE-2023-1389 attack"; http.uri; content:"/cgi-bin/luci/\;stok=/locale?form=country"; nocase; sid:1000001; rev:1;)
其中:
any any -> any any
表示“任意源 IP、源端口、目的 IP、目的端口”msg:"CVE-2023-1389 attack"
是报告信息http.uri; content:"/cgi-bin/luci/;stok=/locale?form=country"; nocase;
表示在 url 中匹配指定的字符串,忽略大小写sid:1000001
指定了规则 ID,用于区分不同规则rev:1
表示这是规则的第一版
执行 curl "http://192.168.25.21:8000/cgi-bin/luci/;stok=/locale?form=country"
,观察到以下 eve 记录:
{
"timestamp": "2024-12-31T15:37:28.311787+0800",
"flow_id": 204480197161792,
"in_iface": "ens224",
"event_type": "alert",
"src_ip": "192.168.25.22",
"src_port": 44386,
"dest_ip": "192.168.25.21",
"dest_port": 8000,
"proto": "TCP",
"pkt_src": "wire/pcap",
"tx_id": 0,
"alert": {
"action": "allowed",
"gid": 1,
"signature_id": 1000001,
"rev": 1,
"signature": "CVE-2023-1389 attack",
"category": "",
"severity": 3
},
"http": {
"hostname": "192.168.25.21",
"http_port": 8000,
"url": "/cgi-bin/luci/;stok=/locale?form=country",
"http_user_agent": "curl/7.88.1",
"http_content_type": "text/html",
"http_method": "GET",
"protocol": "HTTP/1.1",
"status": 404,
"length": 0
},
"app_proto": "http",
"direction": "to_server",
"flow": {
"pkts_toserver": 4,
"pkts_toclient": 4,
"bytes_toserver": 393,
"bytes_toclient": 792,
"start": "2024-12-31T15:37:28.309753+0800",
"src_ip": "192.168.25.22",
"dest_ip": "192.168.25.21",
"src_port": 44386,
"dest_port": 8000
}
}
在阅读源码的过程中,我们会多次运行 Suricata 程序。为了可复现性,我们不宜依赖于局域网中的实时流量,而应使用 pcap 文件重放流量。指令如下:
sudo suricata -c suricata.yaml -s tplink.rules -r dump.pcap
# i: suricata: This is Suricata version 7.0.8 RELEASE running in USER mode
# W: detect: No rule files match the pattern /usr/local/var/lib/suricata/rules/suricata.rules
# i: threads: Threads created -> RX: 1 W: 16 FM: 1 FR: 1 Engine started.
# i: suricata: Signal Received. Stopping engine.
# i: pcap: read 1 file, 100 packets, 16315 bytes
这次,eve 日志不在 /usr/local/var/log/suricata
下,而是在运行目录下。产出与实时监听模式相比,多了一个 pcap_cnt
字段。
0x05 调试模式编译
我们希望追踪 http 请求被处理的过程。然而,我们面前的文件实在太多,且大多数文件与业务无关。想要找到关键逻辑,一个很好的办法是把程序跑一遍,用性能分析工具观察哪些函数被最多次调用。我们选择 gprof 工具。
重新 configure、make:
./configure --enable-shared=no --enable-static=yes --enable-gccprofile=yes CFLAGS="-g -O0"
# 最终生效的 CFLAGS 是:-g -O0 -fPIC -std=c11 -pg -march=native -I${srcdir}/../rust/gen -I${srcdir}/../rust/dist
make -j16
du -h src/suricata
# 101M src/suricata
运行:
sudo ./suricata-dbg/src/suricata -c ./suricata-dbg/suricata.yaml -s tplink.rules -r 100w.pcap
# i: suricata: This is Suricata version 7.0.8 RELEASE running in USER mode
# W: detect: No rule files match the pattern /usr/local/var/lib/suricata/rules/suricata.rules
# i: threads: Threads created -> RX: 1 W: 16 FM: 1 FR: 1 Engine started.
# i: suricata: Signal Received. Stopping engine.
# i: pcap: read 1 file, 1000000 packets, 124916062 bytes
运行结束之后,目录下产生了 gmon.out
文件。用 gprof2dot 生成调用图:
gprof ./suricata-dbg/src/suricata gmon.out > profile.txt
python3 gprof2dot.py profile.txt | dot -Tsvg -o output.svg
结果如下(矢量图,可以放大):
可以发现,关键逻辑应该在 FlowWorker
中。我们日后阅读源码,应当予以关注。
0x06 运行模式
文档提到,Suricata 有不同的运行模式,我们看一眼当前支持的 runmode:
$ ./suricata-dbg/src/suricata --list-runmodes
------------------------------------- Runmodes ------------------------------------------
| RunMode Type | Custom Mode | Description
|----------------------------------------------------------------------------------------
| PCAP_DEV | single | Single threaded pcap live mode
| ---------------------------------------------------------------------
| | autofp | Multi-threaded pcap live mode. Packets from each flow are assigned to a consistent detection thread
| ---------------------------------------------------------------------
| | workers | Workers pcap live mode, each thread does all tasks from acquisition to logging
|----------------------------------------------------------------------------------------
| PCAP_FILE | single | Single threaded pcap file mode
| ---------------------------------------------------------------------
| | autofp | Multi-threaded pcap file mode. Packets from each flow are assigned to a consistent detection thread
|----------------------------------------------------------------------------------------
| PFRING(DISABLED) | autofp | Multi threaded pfring mode. Packets from each flow are assigned to a single detect thread, unlike "pfring_auto" where packets from the same flow can be processed by any detect thread
| ---------------------------------------------------------------------
| | single | Single threaded pfring mode
| ---------------------------------------------------------------------
| | workers | Workers pfring mode, each thread does all tasks from acquisition to logging
|----------------------------------------------------------------------------------------
| NFQ | autofp | Multi threaded NFQ IPS mode with respect to flow
| ---------------------------------------------------------------------
| | workers | Multi queue NFQ IPS mode with one thread per queue
|----------------------------------------------------------------------------------------
| NFLOG | autofp | Multi threaded nflog mode
| ---------------------------------------------------------------------
| | single | Single threaded nflog mode
| ---------------------------------------------------------------------
| | workers | Workers nflog mode
|----------------------------------------------------------------------------------------
| IPFW | autofp | Multi threaded IPFW IPS mode with respect to flow
| ---------------------------------------------------------------------
| | workers | Multi queue IPFW IPS mode with one thread per queue
|----------------------------------------------------------------------------------------
| ERF_FILE | single | Single threaded ERF file mode
| ---------------------------------------------------------------------
| | autofp | Multi threaded ERF file mode. Packets from each flow are assigned to a single detect thread
|----------------------------------------------------------------------------------------
| ERF_DAG | autofp | Multi threaded DAG mode. Packets from each flow are assigned to a single detect thread, unlike "dag_auto" where packets from the same flow can be processed by any detect thread
| ---------------------------------------------------------------------
| | single | Singled threaded DAG mode
| ---------------------------------------------------------------------
| | workers | Workers DAG mode, each thread does all tasks from acquisition to logging
|----------------------------------------------------------------------------------------
| AF_PACKET_DEV | single | Single threaded af-packet mode
| ---------------------------------------------------------------------
| | workers | Workers af-packet mode, each thread does all tasks from acquisition to logging
| ---------------------------------------------------------------------
| | autofp | Multi socket AF_PACKET mode. Packets from each flow are assigned to a single detect thread.
|----------------------------------------------------------------------------------------
| AF_XDP_DEV | single | Single threaded af-xdp mode
| ---------------------------------------------------------------------
| | workers | Workers af-xdp mode, each thread does all tasks from acquisition to logging
|----------------------------------------------------------------------------------------
| NETMAP(DISABLED) | single | Single threaded netmap mode
| ---------------------------------------------------------------------
| | workers | Workers netmap mode, each thread does all tasks from acquisition to logging
| ---------------------------------------------------------------------
| | autofp | Multi-threaded netmap mode. Packets from each flow are assigned to a single detect thread.
|----------------------------------------------------------------------------------------
| DPDK(DISABLED) | workers | Workers DPDK mode, each thread does all tasks from acquisition to logging
|----------------------------------------------------------------------------------------
| UNIX_SOCKET | single | Unix socket mode
| ---------------------------------------------------------------------
| | autofp | Unix socket mode
|----------------------------------------------------------------------------------------
| WINDIVERT(DISABLED) | autofp | Multi-threaded WinDivert IPS mode load-balanced by flow
|----------------------------------------------------------------------------------------
可见一共就是 single
、autofp
、workers
三类。根据文档:
workers
模式下,有多个工作线程,每个线程都运行完整的 pipeline。数据包会均衡分配给各个工作线程。autofp
模式下,有 packet capture 线程(负责捕获包、解码)和 packet processing 线程。pcap 文件默认使用这种模式处理。single
模式类似于workers
模式,但只有一个工作线程。通常用于开发。
那么,我们在 single
模式下,再次运行 Suricata,进行性能分析:
sudo ./suricata-dbg/src/suricata --runmode single -c ./suricata-dbg/suricata.yaml -s tplink.rules -r 100w.pcap
# i: suricata: This is Suricata version 7.0.8 RELEASE running in USER mode
# W: detect: No rule files match the pattern /usr/local/var/lib/suricata/rules/suricata.rules
# i: threads: Threads created -> W: 1 FM: 1 FR: 1 Engine started.
# i: suricata: Signal Received. Stopping engine.
# i: pcap: read 1 file, 1000000 packets, 124916062 bytes
gprof ./suricata-dbg/src/suricata gmon.out > profile.txt
python3 gprof2dot.py profile.txt | dot -Tsvg -o output.svg
结果: