はじめに
ある日、サーバーを確認すると 意図しない cron が設置されていました。
調査を進めると、自分では一度も実装した覚えのない /api/action に対する POST リクエストが、侵入時刻直前に大量に飛んでいることが判明しました。
環境
Nginx(Reverse Proxy)
Next.js(Node.js、localhost:3000)
構成
Internet
↓
Nginx :443
↓ proxy_pass
Next.js (localhost:3000)
事象
cron の作成時刻(2026/01/15 17:15 頃)前後の Nginx access.log を確認すると、以下のようなログが集中していました。
79.110.49.221 - - [15/Jan/2026:17:15:40 +0900] "POST /api/action HTTP/1.1" 500 207 "-" "Mozilla/5.0" "-"
79.110.49.221 - - [15/Jan/2026:17:15:42 +0900] "POST /_next/data HTTP/1.1" 444 0 "-" "Mozilla/5.0" "-"
79.110.49.221 - - [15/Jan/2026:17:15:54 +0900] "POST / HTTP/1.1" 444 0 "-" "Mozilla/5.0" "-"
ポイントは以下です。
- / や /_next/data → 444(Nginxで遮断)
- /api/action → 500(アプリ側でエラー)
違和感①
今 /api/action に POST すると 404 になる
curl -X POST https://example.com/api/action
# → 404
しかしログ上では 明確に 500。
→「実装した覚えのない API が、なぜ 500 を返すのか?」
error.log から分かった決定的事実
同時刻の error.log を確認すると、以下のログがありました。
a client request body is buffered to a temporary file
/var/lib/nginx/tmp/client_body/0000000177,
request: "POST /api/action HTTP/1.1"
これは何を意味するか?
- POST ボディが Nginx のメモリに収まらないほど大きい
- /api/action 宛ての 実体のあるリクエストボディ が存在
- Nginx は アプリにリクエストを転送している
「/api/action が存在した」証拠ではなく、
「/api/action が proxy_pass でアプリに届いていた」証拠
Nginx 設定の確認
location / {
proxy_pass http://localhost:3000;
}
この設定により、
- /api/action というパスが 存在しなくても
- そのまま Next.js 側に転送される
という構成でした。
なぜ「404ではなく500」だったのか
結論として、以下の可能性が高い。
- /api/* をまとめて処理する共通処理(body parser 等)が存在
- 攻撃者が 巨大な POST ペイロード を送信
- ルーティングに到達する前に アプリが例外で落ちる
- 結果として 500 が返る
つまり、
/api/action が「実装されていた」のではなく
「存在しないパスでも処理途中でクラッシュした」
cron が仕込まれた理由(推測)
ログだけでは断定できませんが、状況証拠から以下が考えられる。
- 大容量 POST による処理系の不具合
- 一時的に RCE 相当の状態 が成立
- cron を設置される
再発防止のために行った対策
- POST サイズ制限
client_max_body_size 2m;
- /api/action を明示的に遮断
location = /api/action {
return 444;
}
学び
- 「実装していない API = 安全」ではない
- proxy_pass 配下では
存在しないパスでもアプリに届く - 500 は「サーバが悪い」のではなく
「アプリが何かを処理しようとした」証拠 - Nginx の error.log は
攻撃内容を語ることがある
おわりに
今回の件は、
- アクセスログ
- error.log
- 設定ファイル
を時系列で突き合わせることで、かなり確度の高い推測ができました。
同じように
「見覚えのない API が叩かれている」
「404 のはずなのに 500 が出ている」
という状況に遭遇した人の参考になれば幸いです。