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?

nginxのlocation選択ルールと4つの設定ミスの罠(認証バイパス・aliasパストラバーサル対策)

0
Posted at

nginxの location は、多くの人が思っているような「設定ファイルの上から順に最初に一致したもの」ではありません。この誤解のせいで、認証をかけたはずのパスがすり抜ける、公開ディレクトリの外のファイルが読めてしまうといった事故が起きます。

  • 対象: nginx でサイトを運用している Web 担当者・制作会社・インフラ担当(中級者)
  • 解決すること: location の評価順を正しく理解し、認証バイパス・パストラバーサル・ヘッダ消失につながる 4 つの設定ミスを塞ぐ
  • 前提: 攻撃手順は書きません。すべて「同じ穴を作らない」ための設定と確認方法です

location の評価順は「書いた順」ではない

nginx は次の順序で location を選びます。これは設定ファイルの記述順とは無関係です。

  1. 完全一致 = /path … 一致したら即決定(以降を見ない)
  2. ^~ 付きの前方一致 … 一致する前方一致のうち最長のものに ^~ が付いていれば、それを採用し正規表現を評価しない
  3. 正規表現 ~(大文字小文字を区別)/ ~*(区別しない) … 設定ファイルに書いた順で先頭から評価し、最初に一致したものを採用
  4. 前方一致(通常) … 正規表現がどれも一致しなければ、最長の前方一致を採用

ポイントは 「最長の前方一致を覚えておき、正規表現が一致すればそちらを優先する」 という点です。つまり、location /admin/ で認証をかけていても、後ろに書いた正規表現 location ~ \.php$ が先に一致すれば、認証ブロックは丸ごと無視されます。

まずは自分のサーバで、実際に有効になっている設定を展開して確認しておきます。

# 全 include を展開した「最終的に効いている設定」を表示
sudo nginx -T | less

# 変更後は必ず構文チェック → リロード
sudo nginx -t && sudo systemctl reload nginx

罠1: 正規表現 location が認証を素通りする

次はよくある「危ない」書き方です。管理画面に BASIC 認証をかけたつもりが、PHP は認証なしで実行できてしまいます。

# 悪い例: /admin/ に認証をかけたつもり
location /admin/ {
    auth_basic           "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

location ~ \.php$ {
    include        fastcgi_params;
    fastcgi_pass   unix:/run/php/php-fpm.sock;
}

/admin/tool.php へのリクエストは、前方一致 /admin/ より 正規表現 \.php$ が優先されるため、auth_basic を含まない PHP 用 location で処理されます。結果として認証はかかりません。

対策は、保護したい前方一致に ^~ を付けて正規表現より優先させることです。そのうえで、認証ブロックの中で PHP を処理します。

# 良い例: ^~ で正規表現より優先させ、内側で PHP を処理
location ^~ /admin/ {
    auth_basic           "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location ~ \.php$ {
        include        fastcgi_params;
        fastcgi_pass   unix:/run/php/php-fpm.sock;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

location ~ \.php$ {
    include        fastcgi_params;
    fastcgi_pass   unix:/run/php/php-fpm.sock;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

確認は、認証情報なしで 401 が返ることを見ます。

# 401 (Unauthorized) が返れば OK。200 が返るなら素通りしている
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/admin/tool.php

罠2: alias + 末尾スラッシュ不一致でディレクトリの外が読める

alias を使うとき、location と alias で末尾スラッシュの有無がずれていると、公開ディレクトリの外のファイルが参照できてしまう典型的なミスです。

# 悪い例: location に末尾スラッシュが無く、alias に付いている
location /assets {
    alias /var/www/app/public/assets/;
}

この場合、/assets の後ろに付いた文字列がそのまま alias のパスに連結されるため、../ を含むリクエストで /var/www/app/public/assets/ の親ディレクトリへ抜け出せる余地が生まれます(設定情報などが置かれた領域まで到達しうる)。

対策は 2 つ。どちらか片方で十分です。

# 対策A: location と alias の末尾スラッシュを揃える
location /assets/ {
    alias /var/www/app/public/assets/;
}

# 対策B(推奨): alias ではなく root を使う
#   root は「location のパスを document root に付け足す」動きなので、
#   末尾スラッシュのズレによる抜け出しが起きにくい
location /assets/ {
    root /var/www/app/public;   # 実体は /var/www/app/public/assets/...
}

確認は、../ を含むパスが 404 / 403 になり、公開範囲の外に出られないことを見ます。

# 公開域の外に出られないこと(404 か 403 が返る)を確認
curl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/assets/../nginx.conf'

罠3: PHP location に try_files が無く、存在しないパスを FPM に渡す

~ \.php$ の location で、リクエストされた .php が実在するか確認せずに FastCGI へ渡すと、アップロードされたファイルや存在しないパスが意図せず PHP として解釈される余地を残します。

# 悪い例: 実在チェックが無い
location ~ \.php$ {
    include        fastcgi_params;
    fastcgi_pass   unix:/run/php/php-fpm.sock;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

対策は、try_files $uri =404; で実在しない .php を FPM に渡さないこと、そして fastcgi_split_path_info と cgi.fix_pathinfo=0 で PATH_INFO の付け足しを封じることです。

# 良い例
location ~ [^/]\.php(/|$) {
    try_files $uri =404;                       # 実在しない .php は 404
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    include        fastcgi_params;
    fastcgi_pass   unix:/run/php/php-fpm.sock;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

あわせて PHP 側でも次を設定しておきます(php.ini)。

; 存在しないパスを近くの .php に紐付けさせない
cgi.fix_pathinfo=0

罠4: 内側 location の add_header が上位のセキュリティヘッダを消す

nginx の add_header には見落としやすい継承ルールがあります。ある階層に add_header を 1 つでも書くと、上位の階層で定義した add_header はその階層では一切引き継がれません。

server {
    # サイト全体にセキュリティヘッダを付けたつもり
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options        "SAMEORIGIN" always;

    location /api/ {
        # ここで add_header を書いた瞬間、上の 2 つは /api/ では消える
        add_header Cache-Control "no-store" always;
    }
}

この結果、/api/ 配下だけ X-Content-Type-Options や X-Frame-Options が欠落します。対策は、ヘッダ群を 1 つのファイルにまとめて include し、add_header を書く階層すべてで読み込むことです。

# /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options        "SAMEORIGIN" always;
add_header Referrer-Policy        "strict-origin-when-cross-origin" always;
server {
    include snippets/security-headers.conf;

    location /api/ {
        include snippets/security-headers.conf;   # 内側でも読み直す
        add_header Cache-Control "no-store" always;
    }
}

確認は、代表的なパスすべてでヘッダが揃っているかを見ます。

# 各パスで nosniff / X-Frame-Options が出ているか確認
curl -sI https://example.com/         | grep -iE 'x-content-type-options|x-frame-options'
curl -sI https://example.com/api/test | grep -iE 'x-content-type-options|x-frame-options'

まとめ

  • location は「書いた順」ではなく 完全一致 → ^~ 前方一致 → 正規表現(記述順)→ 最長前方一致 の順で選ばれる
  • 罠1: 認証は保護対象に ^~ を付けて正規表現より優先し、内側で PHP を処理する
  • 罠2: alias は location と末尾スラッシュを揃えるか、可能なら root に置き換える
  • 罠3: PHP location には try_files $uri =404; と cgi.fix_pathinfo=0 を付ける
  • 罠4: add_header は書いた階層で上位が消えるので、スニペットを各階層で include する
  • 変更後は nginx -T で最終形を確認し、nginx -t && reload、curl で挙動を検証する

location の優先順位は一度理解すれば応用が効きます。既存サイトでも nginx -T で今の設定を一度棚卸ししておくと、思わぬ素通りに気づけます。

関連記事

本記事のような設定の抜けは、Webサイトを9つの守りでまるごと守るサイトドックでまとめて対策できます → https://sitedock.jp

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?