はじめに
これまで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系の探索パターンや、広域スキャナと手動確認っぽいアクセスの違いをもう少し深掘りしていきたいと思います。

コメント