自前でFortinet風VPNハニーポットを立ててみた:VPS・Cloudflare Tunnel・攻撃分析APIまで

セキュリティ検証
スポンサーリンク

はじめに

これまでT-Potを使ったハニーポット運用や、Elasticsearch / VirusTotal連携による分析自動化を試してきました。

T-Potは非常に強力で、CowrieやDionaeaなど複数のハニーポットをまとめて扱えるのが魅力です。一方で、今回は少し違う方向の検証として、自前でVPN装置風の低対話ハニーポットを立てることにしました。

自前でハニーポットを立てるのは、T-Potのような既存基盤を使うより手間がかかります。しかし、その分だけ次のような調整ができます。

  • どの製品に見せるかを細かく制御できる
  • HTTPレスポンス、Cookie、証明書、404の返し方まで寄せられる
  • 自分が見たいログ形式にできる
  • Cloudflare Tunnel経由で分析画面だけ安全に公開できる
  • 攻撃者の行動を、自分の観点で分類できる

今回作ったのは、Fortinet FortiGate SSL-VPN風に見える低対話型のVPNデコイです。

公開しているのは偽装ログイン画面だけで、実際のVPN機能やコマンド実行機能はありません。目的は侵入させることではなく、インターネット上のスキャナや攻撃者が、公開VPN機器に対してどのようなアクセスをしてくるのかを観測することです。

なぜVPN装置風にしたのか

2020年代後半の攻撃トレンドを見ると、境界防御機器、特にVPN、ファイアウォール、リモートアクセス装置は継続的に狙われています。

攻撃者にとって、VPN装置は魅力的です。侵害できれば、そこから内部ネットワークへ入る足がかりになるためです。

今回のデコイでは、以下のような製品を模倣できるようにしました。

  • Ivanti Connect Secure
  • Fortinet FortiGate SSL-VPN
  • Palo Alto GlobalProtect
  • Citrix NetScaler

ただし、1台のVPSで複数製品を同時に見せるのは不自然です。

同じIPアドレスの80/443番で、IvantiにもFortinetにもPalo Altoにも見えるという状態は、スキャナから見ればかなりハニーポット臭くなります。

そのため今回は、1台だけ運用する前提で Fortinet単一機器として振る舞う構成にしました。

DECOY_PRIMARY_VENDOR=fortinet
STRICT_SINGLE_VENDOR=1

STRICT_SINGLE_VENDOR=1 にすると、主ベンダ以外の探索パスは基本的に404へ倒します。収集網羅性は少し落ちますが、「単一のFortinet機器らしさ」を優先する判断です。

全体構成

構成は次のようにしました。

Internet
  |
  | 80 / 443
  v
Xserver VPS
  |
  +-- edge        OpenResty / TLS終端 / 80,443公開
  |
  +-- vpn-decoy   Flask / 偽装VPNログイン画面 / JSONLログ出力
  |
  +-- api         集計ダッシュボード / JSON API / 外部ポート非公開
  |
  +-- cloudflared Cloudflare Tunnel / stats用のみ外部公開

ハニーポット本体はVPSの80/443で公開します。

一方、分析画面はVPSの公開ポートには出さず、Cloudflare Tunnel + Cloudflare Accessで stats.example.com のような管理用URLだけ公開します。

ポイントは、ハニーポット本体にはCloudflare Proxyを被せないことです。

Cloudflareのオレンジ雲を使うと、送信元IP、TLS証明書、TLS指紋、HTTP挙動がCloudflare化します。攻撃収集用のデコイとしては、Fortinetらしさが落ちます。

そのため、役割を分けます。

ハニーポット本体:
  162.43.51.48:80/443
  または vpn.example.com Aレコード DNS only

分析画面:
  stats.example.com
  Cloudflare Tunnel + Access認証

VPSの準備

今回はXserver VPSを使いました。

スペックは次の構成です。

OS: Ubuntu 24.04 LTS
RAM: 2GB
CPU: 3Core
SSD: 50GB

1GBでも動く可能性はありますが、Docker buildやログ保存を考えると2GBの方が楽です。

OCI Always Freeも候補にしましたが、Ampere A1の無料枠は在庫不足で作れないことが多く、今回はXserver VPSに切り替えました。

SSHのハーデニング

まずSSHは鍵認証だけにします。

VPSの外側パケットフィルタでは、最初に22番を自分のIPだけ許可しました。

SSHで入れることを確認した後、サーバー側でパスワード認証を無効化します。

cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#\?PubkeyAuthentication .*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config

sshd -t
systemctl reload ssh

秘密鍵はローカルに置きますが、Gitには絶対に入れません。

*.pem
*.key
id_*

鍵ファイルの権限も絞ります。

chmod 600 ssh.pem

Dockerの導入

Ubuntu 24.04環境で docker-compose-plugin が標準aptから見つからなかったため、Docker公式リポジトリを追加しました。

apt update
apt install -y ca-certificates curl gnupg git jq logrotate cron

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" \
  > /etc/apt/sources.list.d/docker.list

apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now docker

確認します。

docker --version
docker compose version

アプリの配置

GitHubリポジトリがprivateの場合、VPS上にPersonal Access Tokenを残すのは避けたいところです。

今回は手元からSSH経由で転送しました。

ssh root@<VPS_IP> 'mkdir -p /opt/vpn-decoy'

tar --exclude=.git --exclude=.env --exclude=logs --exclude='*.jsonl' -czf - . \
  | ssh root@<VPS_IP> 'tar -xzf - -C /opt/vpn-decoy'

VPS上で設定ファイルを作ります。

cd /opt/vpn-decoy
cp .env.example .env

Fortinet単一機器として動かします。

sed -i 's/^DECOY_PRIMARY_VENDOR=.*/DECOY_PRIMARY_VENDOR=fortinet/' .env

ログディレクトリはコンテナ内の非rootユーザーが書き込めるようにします。

mkdir -p logs
chown -R 10001:10001 logs

起動します。

docker compose up -d --build vpn-decoy edge api

確認します。

docker compose ps
curl -I http://127.0.0.1/
curl -k -I https://127.0.0.1/

外部からも確認します。

curl -I http://<VPS_IP>/
curl -k -I https://<VPS_IP>/

Fortinetとして動いていれば、//remote/login?lang=en へ302されます。

HTTP/2 302
location: /remote/login?lang=en
set-cookie: SVPNNETWORKCOOKIE=...

ログイン画面は次のURLで200になります。

https://<VPS_IP>/remote/login?lang=en

自己署名証明書について

ブラウザで見ると証明書エラーが出ます。

これは意図通りです。

Fortinet風に自己署名証明書を生成しています。

CN=FGT60FTK00000000
O=Fortinet
OU=FortiGate
emailAddress=support@fortinet.com

curl でも -k なしでは失敗します。

curl: (60) SSL certificate problem: self signed certificate

実機FortiGateでも自己署名証明書のまま運用されているケースはあるため、ハニーポットとしては許容範囲と判断しました。

外向き通信の遮断

ハニーポットで重要なのは、踏み台化リスクを抑えることです。

このデコイはコマンド実行をしない低対話型ですが、念のためデコイ用コンテナの新規アウトバウンドを遮断します。

注意点として、Dockerコンテナの通信はホストの OUTPUT ではなく、FORWARD / DOCKER-USER を通ります。

そのため、DOCKER-USER にルールを入れます。

ip route get 1.1.1.1

外向きインターフェイスが ens3 なら、次のようにします。

iptables -I DOCKER-USER -s 172.31.10.0/24 -o ens3 -m conntrack --ctstate NEW -j DROP

172.31.10.0/24 はデコイ用のcaptureネットワークです。

一方、Cloudflare Tunnel用の cloudflared は別のmgmtネットワークに置いているため、Cloudflareへの外向き通信は維持します。

systemdで永続化しました。

cat > /usr/local/sbin/vpn-decoy-egress-lockdown.sh <<'EOF'
#!/bin/sh
set -eu
EXT="$(ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}')"
[ -n "$EXT" ]
iptables -C DOCKER-USER -s 172.31.10.0/24 -o "$EXT" -m conntrack --ctstate NEW -j DROP 2>/dev/null \
  || iptables -I DOCKER-USER -s 172.31.10.0/24 -o "$EXT" -m conntrack --ctstate NEW -j DROP
EOF

chmod +x /usr/local/sbin/vpn-decoy-egress-lockdown.sh
cat > /etc/systemd/system/vpn-decoy-egress-lockdown.service <<'EOF'
[Unit]
Description=Block new outbound traffic from vpn-decoy capture network
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/vpn-decoy-egress-lockdown.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now vpn-decoy-egress-lockdown.service

確認します。

iptables -S DOCKER-USER

ログの確認

ログはJSONL形式です。

/opt/vpn-decoy/logs/events.jsonl

確認例です。

tail -f /opt/vpn-decoy/logs/events.jsonl | jq .

送信元IPランキングを見るなら次のようにします。

jq -r '.src_ip' /opt/vpn-decoy/logs/events.jsonl | sort | uniq -c | sort -nr | head

アクセスパスランキングです。

jq -r '.path' /opt/vpn-decoy/logs/events.jsonl | sort | uniq -c | sort -nr | head

攻撃シグネチャに一致したものだけを見る場合です。

jq 'select(.is_exploit_attempt == true)' /opt/vpn-decoy/logs/events.jsonl

ログローテーションも設定しています。

hourly
maxsize 500M
rotate 168
gzip
copytruncate

ローテ済みログも集計対象にしています。

Cloudflare Tunnelで分析画面を公開する

分析画面はVPSのポートとしては公開しません。

Cloudflare Tunnelで stats.secsitex.com のようなURLを作り、Cloudflare Accessで自分だけ入れるようにします。

Cloudflare Zero TrustでTunnelを作成し、Docker connectorを選びます。

VPS上の .env にTunnel tokenを入れます。

cd /opt/vpn-decoy
nano .env
TUNNEL_TOKEN=...

起動します。

docker compose up -d cloudflared
docker compose logs --tail=80 cloudflared

Cloudflare側でTunnelがHealthyになったら、Published application routeを追加します。

Hostname: stats.secsitex.com
Service:  http://api:8090

ここで重要なのは、stats 用にAレコードを作らないことです。

Tunnel routeを追加すると、Cloudflareが自動で次のようなCNAMEを作ります。

stats.secsitex.com CNAME <tunnel-id>.cfargotunnel.com

一方、ハニーポット本体にドメインを付ける場合はAレコードを使います。

vpn.secsitex.com A <VPS_IP> DNS only

オレンジ雲のProxyは使いません。

Cloudflare Accessで認証する

分析画面はログや送信元IPを含むため、公開しっぱなしにしません。

Cloudflare Accessで自分のメールアドレスだけ許可します。

設定例です。

Application domain: stats.secsitex.com
Action: Allow
Include: Emails
Value: 自分のメールアドレス
Session duration: 24 hours

設定後、未認証でアクセスするとCloudflare Accessのログイン画面へ302されます。

https://stats.secsitex.com/

メールOTPが届かない場合は、Zero TrustのAuthentication設定でOne-time PINが有効か確認します。

Google認証を追加して、同じGmailアドレスを許可する構成でも問題ありません。

分析画面で見えるもの

分析画面では、単なる件数だけでなく、攻撃者の行動仮説まで出すようにしました。

表示項目は次の通りです。

  • 総イベント
  • 外部イベント
  • ユニーク外部IP
  • OASTドメイン
  • アクター仮説
  • 判定確度
  • アクセス意図分類
  • CVEランキング
  • 送信元IPランキング
  • パスランキング
  • HTTPメソッド
  • 応答種別
  • User-Agent
  • TLSバージョン
  • IP別プロファイル
  • 直近イベント

APIも用意しています。

GET /summary
GET /assessment
GET /ips
GET /ip?address=<IP>
GET /healthz

たとえば /assessment では、全体の仮説分類が返ります。

{
  "assessment": {
    "stages": [
      {"key": "手動確認またはブラウザ型確認", "count": 9},
      {"key": "VPN/エッジ機器の偵察", "count": 5},
      {"key": "エクスプロイト試行/配送", "count": 3}
    ]
  }
}

IP単位の詳細も見られます。

GET /ip?address=172.174.165.231

返ってくる内容には、以下が含まれます。

  • 件数
  • ユニークパス数
  • 最多パス
  • User-Agent
  • アクセス分類
  • 判定ステージ
  • 判定根拠
  • HTTPメソッド分布
  • 応答種別分布

攻撃者の行動をどう分類するか

今回の分析では、攻撃者の意図を断定するのではなく、観測ログから仮説を立てる形にしています。

分類は次のようにしました。

Fortinet表層スキャン
  / から /remote/login?lang=en への導線確認、favicon取得など。
  製品識別や広域スキャンの可能性。

VPN/エッジ機器の偵察
  /SDK/webLanguage, /remote/fgt_lang, /api/auth/validate-sso など。
  既知のVPN/エッジ機器パスを探している可能性。

広域スキャナ/認証情報探索
  /.env, /.git/config, wp-config.php, .aws/credentials など。
  一般的なWebサーバー向けの認証情報探索。

Fortinetログイン試行/手動確認
  /remote/login へのPOSTやブラウザUser-Agent。
  人間の確認、ブラウザ型ボット、認証試行の可能性。

エクスプロイト試行/配送
  CVE署名一致、非ログインPOST、パストラバーサル、OASTドメイン、ペイロード付きリクエスト。

判定確度は high / medium / low にしています。

ただし、これはあくまでヒューリスティックです。ログだけで「人間が手で狙ってきた」と断定することはできません。

たとえば、ブラウザUser-Agentで /remote/login にアクセスしてきた場合、人間の可能性もありますが、ブラウザ風User-Agentを使う自動化ツールの可能性もあります。

逆に、短時間に多数のパスを探索し、.env.git/config を見に来るものは、広域スキャナや認証情報探索の可能性が高いと見ます。

実際に見えたアクセス

立ち上げ後、短時間でアクセスが来ました。

代表的には次のようなパスです。

/
/remote/login?lang=en
/SDK/webLanguage
/.env
/.git/config
/wp-config.php
/.aws/credentials
/manager/html

Fortinetのログイン画面を見に来るものもあれば、WordPressやGit、AWS credentialsのような一般的なWebスキャンも混ざります。

つまり、VPN風に見せても、インターネット上の広域スキャナはかなり雑にいろいろなパスを投げてきます。

これはT-Potでもよく見る傾向ですが、自前で作ると「どの応答に対して何が来たか」がかなり見やすくなります。

自前ハニーポットの良いところ

T-Potと比べると、自前ハニーポットは機能数では劣ります。

しかし、今回やってみて、自前には自前の良さがあると感じました。

  • 特定テーマに絞れる
  • 偽装の作法を細かく調整できる
  • ログ形式を自分で決められる
  • 低スペックVPSで動かせる
  • 分析APIを自分の仮説に合わせて作れる
  • Cloudflare Tunnelで管理画面だけ安全に出せる

特に、「攻撃者が何を見て、どこで次の行動に移ったのか」を見るには、自分でログ設計できるのが大きいです。

注意点

ハニーポットは、意図的に攻撃を集める仕組みです。

運用するなら、最低限以下は必要です。

  • 本番ネットワークから分離する
  • SSHは鍵認証にする
  • 管理ポートは自分のIPだけ許可する
  • デコイコンテナの外向き通信を遮断する
  • ログローテーションを入れる
  • 分析画面は認証で保護する
  • Cloudflare Proxyをハニーポット本体に被せない

また、ログには送信元IPやUser-Agentが含まれます。記事やレポートで共有する場合は、個人情報や法的な扱いに注意が必要です。

まとめ

今回は、自前でFortinet風VPNハニーポットを立てました。

最終的な構成は次の通りです。

Xserver VPS
Ubuntu 24.04 LTS
Docker Compose
Fortinet SSL-VPN風デコイ
Cloudflare Tunnel
Cloudflare Access
自前分析API

T-Potのような総合ハニーポット基盤も便利ですが、特定の攻撃面を深く見るなら、自前で小さく作るのもかなり有効です。

特に今回は、VPN装置風の表層、自己署名証明書、Cookie、ログイン導線、404応答、Cloudflare Tunnelによる分析画面保護まで、一通り自分で制御できました。

今後は、実際に集まったログをもとに、Fortinet系の探索パターンや、広域スキャナと手動確認っぽいアクセスの違いをもう少し深掘りしていきたいと思います。

コメント

タイトルとURLをコピーしました