攻撃者の報告を自動化する:AbuseIPDB APIとcowrieの統合

前回の記事でcowrieのハニーポットに罠を仕掛け、攻撃者を観察し始めた。毎日攻撃IPをAbuseIPDBに手動で報告していたが、さすがに面倒になってきた。scpでダウンロードして、ブラウザでアップロードして、報告済みリストを更新して、CSVを削除する。6ステップを毎日繰り返すのは学びがない。自動化する時が来た。

Hetznerサーバーを借りた理由をあらためて考える

自動化の話の前に少し脇道に逸れる。

今日bashのコースをやっていてSIGINFOというシグナルがmacOSでしか動かないことに気づいた。Linuxには存在しないシグナルだ。BSDとLinuxはPOSIXという共通の仕様書を元に実装した別の処理系で、互いに独自拡張を持っている。SIGINFOはBSD固有、SIGPWRはLinux固有といった具合だ。

このことを考えていてHetznerを借りた理由を再確認した。

Macは便利だがGUIのためにリソースを大量に消費する。btopで見比べると一目瞭然だ。

Hetzner(Linux)Mac(M2)
CPU使用率1%6〜16%
メモリ使用557 MiB / 3.73 GiB(15%)3.49 GiB / 8 GiB(44%)
プロセス数122621

MacのWindowServerだけでかなりのメモリを持っていく。mediaanaというプロセスはPhotosのAI機能のためにバックグラウンドで機械学習を回している。Macは「全部いい感じにしてあげる」OSなので仕方ない。

Hetznerのサーバーはsshd、fail2ban、systemdなど本当に必要なものしか動いていない。Mac側のリソースを食わずに開発環境を完全分離できる。月額数百円でEPYCサーバーが使えて通信量は20TB。個人で使い切るのはまず無理な量だ。しかもIPレピュテーションは使い続けるほど積み上がっていく。

check_new_attackers.shの問題点

報告スクリプトは以前から動いていたが、今日見直すと問題があった。

ログソースがauth.logだけだった。

# 以前の実装(問題あり)
auth_ips=$(sudo grep "Invalid user\|Connection closed" "$AUTH_LOG" | \
  grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
  sort -u)

これでは本物のSSH(XXXX番)への攻撃しか拾えない。cowrieのハニーポット(XX番)で累計7000パケット超を受けているのに、そのデータが報告に全く使われていなかった。しかも回数も時刻も取っていないので、「何回攻撃したか」という情報がコメントに入らない状態だった。

cowrie.jsonを統合する

cowrieのログはenokiユーザーから直接読めない。

ls -la /home/cowrie/cowrie/var/log/cowrie/
# Permission denied

cowrieグループに本番ユーザーを追加する方法もあるが、攻撃者が本番ユーザーに侵入したときにハニーポットの存在がバレる。なのでsudoで読む方針にした。

cowrie.jsonから必要な情報が取れるか確認した。

sudo cat /home/cowrie/cowrie/var/log/cowrie/cowrie.json | \
  jq -r 'select(.eventid == "cowrie.session.connect") | .src_ip' | \
  sort | uniq -c | sort -rn | head -10
174 120.26.194.219
152 46.101.80.205
125 159.223.49.170
 89 138.197.182.192
 85 176.120.22.47

回数も時刻も取れた。これをauth.logと統合する。

スクリプトの改善

改善のポイントは3つだった。

1. ログソースを両方に拡張

auth.logとcowrie.jsonの両方からIPを抽出して統合する。cowrieからは回数と最初の攻撃時刻も取得する。

# cowrie.jsonからIP・回数・最初の攻撃時刻を抽出
cowrie_data=$(sudo jq -r \
  'select(.eventid == "cowrie.session.connect") | [.timestamp, .src_ip] | @tsv' \
  "$COWRIE_LOG")

# 全IPを統合してユニーク化
all_ips=$(
  echo "$auth_ips"
  echo "$cowrie_data" | awk '{print $2}'
)
all_ips=$(echo "$all_ips" | sort -u)

2. フィルタリングの強化

テスト中に127.0.0.1がcowrie.jsonに記録されているのを発見した。ローカルホストを報告するのは恥ずかしい。プライベートIPを除外するフィルターを追加した。

if echo "$ip" | grep -qE \
  '^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|0\.|169\.254\.)'; then
  continue
fi

さらにCensysやShodanといったセキュリティリサーチャーはorg名とASN番号で除外するようにした。これらは攻撃者ではなくスキャナーなので報告しない。

if echo "$org" | grep -qiE "Censys|Shodan|AS398324|AS398722|AS20473"; then
  echo "除外(リサーチャー): $ip | $org"
  continue
fi

3. 回数の単複対応

細かいが1 attemptsは英語として変なので直した。

attempts_word=$([ "$total" -eq 1 ] && echo "attempt" || echo "attempts")

これで1 attempt174 attemptsと正しく出力される。

AbuseIPDB APIで完全自動化

AbuseIPDBのbulk-report APIは無料アカウントで1日1,000件まで使える。APIキーをアカウントページで発行して安全に保存した。

echo "ABUSEIPDB_API_KEY=xxxxxxxxxxxx" > ~/.abuseipdb
chmod 600 ~/.abuseipdb

スクリプトの最後にAPI報告を組み込んだ。

source "$HOME/.abuseipdb"

response=$(curl -s -X POST https://api.abuseipdb.com/api/v2/bulk-report \
  -H "Key: $ABUSEIPDB_API_KEY" \
  -H "Accept: application/json" \
  -F "csv=@$OUTPUT")

success=$(echo "$response" | jq -r '.data.savedReports // 0')
if [ "$success" -gt 0 ]; then
  ~/projects/update_reported.sh
  rm "$OUTPUT"
fi

報告が成功したら自動でreported_ips.txtを更新してCSVを削除する。失敗した場合はCSVを残してデバッグできるようにした。

結果

以前は6ステップあった手動作業がこれだけになった。

~/projects/check_new_attackers.sh

実行すると今日の攻撃者が一覧表示される。

未報告: 120.26.194.219 → CN | Hangzhou | AS37963 Hangzhou Alibaba Advertising Co.,Ltd. | 174回
未報告: 46.101.80.205 → GB | London | AS14061 DigitalOcean, LLC | 152回
未報告: 159.223.49.170 → SG | Singapore | AS14061 DigitalOcean, LLC | 125回
除外(リサーチャー): 162.142.125.208 | AS398324 Censys, Inc.
除外: 167.94.146.60
...
=== 報告成功: 21件 ===

回数・時刻・org情報が揃った状態でAbuseIPDBに報告される。Hetznerのネットワーク健全性に貢献しながら、自分のIPレピュテーションも守れる。学習コストをそのままコミュニティへの貢献に変換できている。

次はCMDが記録されるのを待つ。攻撃者がhoneyfsの偽ファイルを開いた瞬間に何をしようとしていたか全部わかる。