
maltrail v2.2
公開ブラックリスト、静的マルウェアトレイル、ヒューリスティック分析を活用し、DNS、HTTP、IPトラフィックにおける脅威を識別するリアルタイム悪意トラフィック検出システム。

悪性トラフィック検出システム。 Maltrail は、悪性と知られているものとの接触をネットワーク上で監視し、何が観測されたのか、なぜ悪性と判断されたのかを、一行で伝えます。
"2026-08-07 09:14:22.117034" gw 10.13.13.2 57809 1.1.1.1 53 UDP DNS malware.bakewithdavid.com "asyncrat (malware)" (static)
ルール言語も、チューニングの儀式も、ML (機械学習) も不要です。trail とは、悪性のものに属することが知られているドメイン、URL、IP アドレス、IP:port、または User-Agent のことであり、Maltrail はそれらがネットワーク上 (回線上) に現れたときに通知します。
なぜ Maltrail なのか
ほとんどのネットワーク検出ツールは、挙動 (behaviour) の記述を求めます。Maltrail は、実際のインシデントのほとんどに答える、より単純な問いを投げかけます: このホストは、すでに悪性と判明している相手と通信しているか?
- 150 万以上の trail を、3,000 を超える精選された静的リストと 46 の公開フィードから収集し、毎日更新・増加させています。マルウェア — C2 ドメイン、ドロッパー、スティーラー、APT インフラ — に大きく重み付けされています。なぜなら、実際の侵害で現れるのはそうしたものだからです。
- Trail はプレーンテキストです。 1 行に 1 つのインジケータを、読み取り、grep、プルリクエストが可能なファイルに格納します。だからこそカバレッジは最新の状態を保ち、「なぜこれが発火したのか?」にいつでも答えられるのです。
- 考えなくて済むほど高速です。 現実的なトラフィック構成であれば、シングルコアで 10 GbE リンクを処理できます。パフォーマンス を参照してください。
- ヒューリスティクスは代替ではなく、その上に追加されます — それぞれがイベント内で名前付きで示され、素のスコアで示されることはありません: ポートスキャン、UDP スキャン、Web スキャン、DNS 枯渇、DGA 形状のルックアップ (エントロピーと子音の閾値、過剰な NXDOMAIN)、シンクホール・差し押さえ・パークされたドメイン、長いドメイン、直接 IP および IoT マルウェアのダウンロード、不審なユーザーエージェントとプロキシプローブ。
アーキテクチャ
2 つの独立したプロセスです。1 台のマシンでも複数台でも実行できます。
┌──────────┐ events (UDP or file) ┌──────────┐
│ sensor │ ───────────────────────► │ server │ ◄── browser
└──────────┘ └──────────┘
Rust Python
libpcap + PACKET_FANOUT reporting UI + API
trail matching, heuristics
センサーはローカルにログを記録 (LOG_DIR)、リモートサーバーへ送信 (LOG_SERVER)、またはその両方を行うことができます。既存の SIEM 向けには、syslog 経由の CEF (SYSLOG_SERVER) と Logstash JSON (LOGSTASH_SERVER) も出力します。
パフォーマンス
センサーは Rust で実装され、キャプチャワーカーごとに 1 スレッド、単一の不変 trail ストアを共有します。トラフィック種別ごとのパケットあたりコスト:
| トラフィック | パケットあたり |
|---|---|
| ICMP echo (58 B) | 101 ns |
| TCP SYN (70 B) | 302 ns |
| bulk TLS (1,473 B) | 402 ns |
| DNS query, warm cache (93 B) | 452 ns |
| mixed traffic (866 B average) | 552 ns |
| HTTP request (169 B) | 602 ns |
| DNS query, every name unique (DGA flood) | 1,102 ns |
ワーカーは可変状態を一切共有しないため、そのコストが、コアを追加するごとに得られる性能そのものです。866 バイトの混在トラフィックを再生した結果:
| ワーカー数 | packets/s | Gbit/s | 1 ワーカー比 |
|---|---|---|---|
| 1 | 1,687,991 | 11.69 | 1.00× |
| 2 | 3,209,627 | 22.24 | 1.90× |
| 4 | 5,379,436 | 37.27 | 3.19× |
| 8 | 8,552,231 | 59.25 | 5.07× |
| 16 | 10,165,773 | 70.43 | 6.02× |
1 コアで 10 GbE を飽和させます。 スケーリングは 4 ワーカーまでほぼ線形で、その後は 8 物理コアのマシン上で頭打ちになります。残りは SMT であるためです — ロック競合ではなくハードウェアの限界です。
旧 Python センサーとの比較です。同じ 300,000 パケットのキャプチャを同じ 1,505,265 trailと同一構成で、各 1 ワーカーずつ再生しました — 起動は除外しているため、これは定常状態のパケットコストです。ここでの数値はどちらもプロセス全体のもの (pcap 読み取りとディスパッチを含む) であり、そのためセンサーの数値は、上記で単独測定した 552 ns のパケット経路よりも高くなっています:
| パケットあたり | packets/s | |
|---|---|---|
| センサー (Rust) | 865 ns | 1,156,423 |
| 旧センサー (Python) | 23,448 ns | 42,648 |
| 27倍高速 |
自分で再現できます — ハーネスはコミットされており、両センサーのイベント数を出力するため、スループットの数値を、その正確性の文脈なしに引用することはできません:
python3 sensor/tools/bench_compare.py --packets 300000 --trails ~/.maltrail/trails.csv --repeat 3
メモリ使用量はコア数に応じて増えません。150 万 trail のストアは 68.5 MB で、1.2 秒で構築され、すべてのワーカーが不変に共有します。
デフォルトはキャプチャワーカー 1 つで、約 110 万 packets/s を処理し、ほぼすべてのセンサーホストに十分です。追加ワーカーは明示的なオプトイン (CAPTURE_FANOUT) です。カーネルはキャプチャをフローハッシュする一方、スキャンヒューリスティクスは送信元ごとにカウントするためです。1 ワーカーが発するヒューリスティックアラートのうち、2 ソケットでは 91%、4 では 86%、8 では 65% が維持されます。厳密な trail 検出は、ワーカー数によらず同一です。maltrail_capture_dropped_total が指示したときにスケールアウトしてください。それまでは不要です。
AMD Ryzen 7 PRO 4750U (8 物理コア)、ヒューリスティクス有効、実トライルセット、3 回の実行の最速値。実行やハードウェアによって比率は 14〜27 倍の範囲で変動します。上記のパケットあたりコストが、ワーカーが吸収できるトラフィック量を左右します。これらはソフトウェア経路の数値です — 実環境の NIC ではドライバとリングのコストが加わるため、ご自身のハードウェアで測定し、maltrail_capture_dropped_total を監視してください。方法、プロトコル別の内訳、命令数、プロファイラ出力は sensor/docs/REPORT.md にあります。
クイックスタート
Linux、libpcap、センサー用に Rust 1.74+、サーバー用に Python 3.7+ が必要です。
x86_64 と aarch64 向けのビルド済みセンサーバイナリが、SHA-256 チェックサム付きで各 リリース に添付されています。そのため Rust ツールチェーンはソースからのビルドにのみ必要です。それでもビルドする場合:
git clone --depth 1 https://github.com/stamparm/maltrail.git
cd maltrail
# 1. build the sensor
cd sensor && cargo build --release && cd ..
# 2. let it capture without running as root
sudo setcap cap_net_raw,cap_net_admin=eip sensor/target/release/maltrail-sensor
# 3. give it somewhere to write events (LOG_DIR, /var/log/maltrail by default)
sudo install -d -o "$USER" -g "$USER" -m 750 /var/log/maltrail
# 4. check the deployment before trusting it — exits non-zero if anything is wrong
sensor/target/release/maltrail-sensor -T
# 5. run it (first start builds the trail set; takes a minute)
sensor/target/release/maltrail-sensor
別のターミナル、または別のマシンで:
python3 server.py
次に http://127.0.0.1:8338 を開き、maltrail.conf (USERS) の認証情報でログインします。
-T は「これで動くのか?」を確認するためのショートカットです。構成、trail、ホワイトリスト、ログディレクトリ、キャプチャフィルタ、権限を検証し、何が不足しているかを正確に教えてくれます:
[o] log directory: '/var/log/maltrail' is writable
[o] capture privileges: CAP_NET_RAW present
[o] capture filter: udp or icmp or (tcp and (tcp[tcpflags] == tcp-syn or port 80 or port...
[o] interface: any
[o] workers: 16 (PACKET_FANOUT required; verify with tools/fanout_check.py as root)
[o] whitelist: 3440 entries, 18 CIDR range(s)
[o] trails: 1505265 loaded (0 malformed row(s)), ipv4=144758 ipv4:port=253517 ipv6=2014 wildcard=29
[o] heuristics: on (disabled: none)
[i] configuration test PASSED
手順 2 または 3 を省略することは、起動しても何も検知しないセンサーになってしまう最も一般的な原因です。-T はその両方を指摘します。
サービスとして実行
sudo useradd --system --no-create-home --shell /usr/sbin/nologin maltrail
sudo rsync -a --exclude .git . /opt/maltrail/
sudo cp /opt/maltrail/maltrail-{server,sensor}.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now maltrail-server maltrail-sensor
これでインストールは完了です — 作成するディレクトリも、setcap もありません。ユニットは systemd の LogsDirectory=/StateDirectory= により /var/log/maltrail (イベント) と /var/lib/maltrail (trail セット) を作成・所有し、両プロセスを権限のない maltrail ユーザーとして読み取り専用ファイルシステム上で実行し、センサーには CAP_NET_RAW と CAP_NET_ADMIN だけを正確に付与します — それ以外は何もなく、どこにも root はありません。センサーは ExecStartPre として -T を実行するため、壊れたデプロイは何も分からないまま実行される代わりに、systemctl start の時点で失敗します。
確認方法: systemctl status maltrail-sensor と journalctl -u maltrail-sensor -f を実行します。
Docker
docker compose -f docker/docker-compose.yml up -d
docker/README.md を参照してください。
構成
すべては maltrail.conf に集約され、[Sensor] と [Server] に分かれています。特に知っておく価値のあるオプション:
| オプション | 機能 |
|---|---|
MONITOR_INTERFACE | キャプチャするインターフェース、または any |
CAPTURE_FILTER | BPF フィルタ。デフォルトでは、バルクの回線レートトラフィックをユーザースペースに持ち込まない |
PROCESS_COUNT | キャプチャワーカー数。コアごとに 1 つが妥当なデフォルト |
LOG_DIR | イベントの書き込み先 (/var/log/maltrail) |
TRAILS_FILE | 構築された trail セットの場所 (~/.maltrail/trails.csv。ユニット実行時は /var/lib/maltrail) |
LOG_SERVER | ローカル記録の代わりに、またはローカル記録に加えて、イベントをリモートサーバーへ送信する |
STATS_ADDRESS | Prometheus メトリクスを公開する (センサー。設定時のみ有効) |
UPDATE_PERIOD | trail の更新頻度 |
USER_WHITELIST | 独自の「絶対にアラートしない」リスト |
CUSTOM_TRAILS_DIR | 同梱の trail に加える独自の trail |
Trails
trails/static/malware/asyncrat.txt # one indicator per line
trails/static/malicious/…
trails/static/suspicious/…
trails/feeds/*.py # public feeds, pulled on update
インジケータの追加は、テキストファイルに行を 1 行足すだけです。フィードの追加は、小さな Python モジュールを 1 つ加えるだけです。どちらも通常のプルリクエストであり、この敷居の低さこそが、セットが有用であり続ける理由です。
独自のインジケータは CUSTOM_TRAILS_DIR に置きます。通知されたくないものはすべて USER_WHITELIST に置きます。
イベント
検知ごとに 1 行、空白区切り、必要に応じて CSV クォート:
"<time>" <sensor> <src_ip> <src_port> <dst_ip> <dst_port> <proto> <type> <trail> "<info>" <reference>
type は何が一致したか — DNS、IP、IPORT、URL、PATH、HTTP、UA、PORT — を示し、info はなぜ悪性と判断されたか、reference は trail の出典 ((static)、フィード名、または (heuristic)) を示します。
運用
-
-Tは構成を検証して終了します。デプロイのゲートとして使用でき、systemd ユニットはExecStartPreとして実行します。 -
STATS_ADDRESSは Prometheus メトリクスを公開します。アラートを設定する価値のある 4 つで、いずれも このセンサーはあなたが思っているものを検知していない ことを意味します:メトリクス 意味 maltrail_up == 0キャプチャワーカーが 1 つも稼働していない — このホストは監視されていません rate(maltrail_capture_dropped_total)リングがパケットをドロップしている — 検知の取りこぼし rate(maltrail_local_log_errors_total)検知は生成されたが失われた maltrail_trail_generationnot advancingtrail の更新が停止している 他にも有用なもの:
maltrail_log_dir_free_bytes(後述) とmaltrail_state_saturations_total。後者は状態枯渇フラッドがヒューリスティクスを縮小したときに非ゼロになります。厳密な trail マッチングは、設計上その影響を受けません。 -
systemctl reload(SIGHUP) は再起動なしで trail を再読み込みします。他の手段で更新された trail も、アトミックスワップにより 1 秒以内に反映されます — 再起動もパケットドロップもありません。 -
凝縮オブザーバブルストア (
USE_CONDENSED_STORAGE、meta.sqlite) は、サーバーの/meta新規性ビューとレトロハントビューにデータを供給するもので、旧センサーが生成するのと同じ形式で書き込まれ、両者はパリティハーネスによって行ごとに比較されます。センサー間の意図的な違いはすべてsensor/docs/COMPATIBILITY.mdに列挙されています。
イベント保持
Maltrail はイベントの証跡を決して削除しません。 ログを失効させる保持設定はなく、それは意図的なものです。これらはインシデント後に振り返るための記録であり、静かに破棄してしまうツールは、それらが最も必要になる一週間において、役に立たないどころか有害です。
そのため、空き容量は無視するものではなく、運用するものになります:
- 耐久性のあるコピーをサーバー外へ送ります。
LOG_SERVER(またはSYSLOG_SERVER/LOGSTASH_SERVER) により、サーバーまたは SIEM が記録の正規システムとなり、センサーのローカルファイルはバッファになります。これが保持戦略です。ローカルディスクは保持戦略ではありません。 maltrail_log_dir_free_bytesに十分な余裕をもってアラートを設定します。-Tもこれを報告し、10 GB 未満になると警告します。ゼロに達するとセンサーは追記できなくなり、検知が失われます。- アーカイブはご自身の判断で行います。 容量が必要であれば、ご自身のスケジュールで古い日次ログを圧縮または移動してください。レポート UI は履歴ログをシーク可能なプレーンファイルとして提供するため、その場で圧縮すると、それらの日がインターフェースから消えます — 別の場所にアーカイブしてください。
ポリシーで削除が要求される場合 (イベントログには IP アドレスとドメインが含まれ、一部の法域では個人データに該当します)、それは運用者の明示的な判断です — センサーのデフォルトが静かに実行するのではなく、ご自身のツールで意図的に行ってください。
ドキュメント
sensor/docs/INSTALL.md | インストール、権限、構成、トラブルシューティング |
sensor/docs/ARCHITECTURE.md | センサーの内部動作 |
sensor/docs/COMPATIBILITY.md | 旧センサーとの意図的な違いのすべて |
sensor/docs/REPORT.md | 測定値、プロファイル、テスト結果 |
sensor/docs/ROADMAP.md | 未解決の項目 |
old/README.md | 旧 Python センサー。参照用およびテストオラクルとして維持 |
コントリビューション
最も価値のあるコントリビューションは trail です。出典付きで、適切なファイルに 1 行追加するだけです。フィード、バグ報告、センサーの開発も同様に歓迎します。
センサーの完全なゲートは 1 つのコマンドです:
bash sensor/tools/check.sh
これはフォーマット、リント、debug と release の両方のプロファイルでのテストスイートを実行し、コーパスを現在のセンサーと旧 Python センサーの両方で再生して、バイト単位で同一のイベントを要求します。Python 側は bash tests/run.sh になります。
ライセンス
MIT。LICENSE を参照してください。