個人学習プロジェクト 「Security Learning Lab(SLL)」 の学習記録です。
今回は、前回OWASP ZAPで検出された5件の指摘をNginxの設定変更で解消し、再スキャンでアラートがゼロになるまでを実践した内容についてまとめます。
学習時間
約1.5時間
今日やったこと
- EC2へのSSH接続がタイムアウトし、ルーター交換でグローバルIPが変わっていたことが原因と判明。セキュリティグループのSSH許可IPを「マイIP」で更新して解決
- Docker Composeの構成を確認し、Nginxコンテナがイメージのデフォルト設定をそのまま使っていることを再確認
- ホスト側に
nginx/default.confを作成し、server_tokens offと3つのセキュリティヘッダ(X-Content-Type-Options、X-Frame-Options、Content-Security-Policy)を追加 - docker-compose.ymlに
volumesを追加し、設定ファイルをコンテナ内のNginx設定ディレクトリ(confディレクトリ配下のdefault.conf)に読み取り専用でマウント docker compose up -dでNginxコンテナだけが再作成されることを確認し、curl -Iでヘッダの付与とバージョン表示の消失を確認- ZAPで再スキャンし、5件が1件(CSPのディレクティブ定義漏れ)に減ったことを確認
- CSPに
form-actionとbase-uriを追加してdocker compose restart web、再スキャンでアラート0件を確認 - 修正後のHTMLレポートを出力し、修正前のレポートと並べて保存
学習内容
マウントで設定をコンテナに持ち込む
コンテナは使い捨てのため、中の設定ファイルを直接書き換えても、コンテナを作り直した瞬間に元に戻ってしまう。ホスト側にファイルを置き、docker-compose.ymlの volumes でコンテナ内の決まった場所に重ねて見せるのがマウント。置かれているファイルを書き換えるのではなく、Nginxが読みに来る場所に「読んでほしいファイルを新たに置く」という考え方で、コンテナを何度作り直しても同じ設定が反映される。末尾の :ro は読み取り専用の指定で、必要最小限の権限だけを渡す基本に沿ったもの。
server_tokens offの効果
Nginxはレスポンスヘッダの Server と、404などのエラーページ本文の2箇所にバージョンを名乗っている。server_tokens off はその両方からバージョン番号を消す。「Nginxであること」までは隠せないが、「どのバージョンか」が分からなければ既知の脆弱性を狙い撃ちしにくくなる。この1行で前回のLow 2件(Serverヘッダ、Banner Leak)が同時に解消した。
add_headerのalways
Nginxの add_header は、既定では200番台などの正常応答にしかヘッダを付けない。末尾に always を付けると404などのエラー応答にも付くようになる。前回のBanner Leakが /robots.txt の404で検出されていたように、診断ツールはエラーページも見ているので、always がないと再検出される。
default-srcを引き継がないCSPディレクティブ
CSPの default-src は「リソースをどこから読み込むか」の既定値で、script-src や img-src などは未指定ならこれを引き継ぐ。一方で frame-ancestors(どこに埋め込めるか)、form-action(フォームをどこに送るか)、base-uri(基準URLの差し替え防止)は読み込みとは別種の制御のため引き継がれず、明示的に書かないと未設定扱いになる。form-action 'self' は、ページが改ざんされてもフォームの送信先を攻撃者のサイトに向けられないようにする、出口側の防波堤。
Composeの差分検知とrestart
docker-compose.ymlを変更して up -d すると、Composeは変更があったサービスだけを再作成し、変更のないサービス(今回はMySQL)には触れない。一方、マウントしたファイルの中身だけを変えた場合はComposeから見て設計に変更がないため、docker compose restart web で明示的に再起動して設定を読み直させる必要がある。
困ったこと・つまずいたこと
SSH接続がタイムアウトした
- 何に困ったか:1週間ぶりにSSH接続を試みたところ「Operation timed out」で接続できなかった
- どう解決したか:直前にルーターを交換しており、グローバルIPが変わったことでセキュリティグループの「自分のIPのみ許可」から外れていた。EC2コンソールでSSHルールのソースを「マイIP」で更新し、旧IPの行を削除して解決。実務で定番の「接続元IPが変わった」問い合わせを自分で体験する形になった
修正後のスキャンで新しい指摘が出た
- 何に困ったか:5件は消えたが、代わりに「CSP: CSP重要ディレクティブの定義漏れ」というMediumの指摘が新たに出た
- どう解決したか:アラートの詳細を確認し、
form-actionがdefault-srcにフォールバックしないディレクティブだと分かった。CSPにform-action 'self'; base-uri 'self'を追記して再スキャンし、0件になった
今日の学び・気づき
- 「検出→修正→再診断→クリア」を自分の環境で一周できた。仕事で提供している再診断サービスの流れが、手を動かすことで具体的なイメージになった
- 修正によって前回の指摘が消えても、修正内容に対して新たな指摘が出ることがあると体験した。「CSPが無い」が解消されたら次は「CSPの中身」を見られる、という診断の階層構造を実感した
- マウントは「置かれているものを書き換える」のではなく「読み込んでほしいものを新たに置く」仕組みだと理解できた。設定の正本をホスト側に持つことで、コンテナを使い捨てにできる
- 設定を1行足すだけで直る項目でも、
alwaysの有無やフォールバックの仕様のような細部を知らないと、ツールには「未対応」と判定される。対策の「つもり」と実際の効き目は別物だと感じた - ルーター交換でSSHが繋がらなくなったのは、セキュリティグループが正しく機能している証拠でもあった。不便さと安全性は表裏一体だと改めて思った
今日の達成
Zero Alerts
ZAPの指摘5件をNginx設定の修正で解消し、再スキャンでアラート0件を達成した。
次回やること
- Burp Suiteを導入し、ZAPとの違いを比べながら手動診断の基本を学ぶ
今日のひとこと
直近でWi-Fiルーターを交換していたので、SSH接続できなかったときは少し焦った。それはさておき、検出した問題を修正するまでの作業を実施! 実際にセキュリティチェックして、修正対応を行うまでの流れを経験すると、自分の仕事への理解が一段深まったように感じる。少し感動すら覚えた。知識だけでなく、実践を通じて学ぶことの大切を痛感する。

