Nginx の location でアクセス制限をかける際の落とし穴

石川英典

プライム・ストラテジー「KUSANAGI」開発チームの石川です。

KUSANAGIでも採用している nginx は非常に高性能なWebサーバです。
Webサーバとして長い間使われてきた Apache HTTPD Server と比較すると設定ファイルの記述が異なるため、設定に戸惑うケースもあります。

Apache HTTPD Serverの .htaccess ファイルに相当する処理を、 nginx の location ディレクティブで行えることを、 以前にコラム Nginx の location の書き方で注意すること で説明しました。
今回は前回の説明に加えて、 location ディレクティブで間違えがちなアクセス制限を設定する際の落とし穴を説明します。

サイト全体にアクセス制限をかける際の落とし穴

Basic認証を通ると deny all したパスもアクセスできる場合がある

Nginx でサイト全体に Basic 認証を行う場合は server に以下のような記述を追加します。

server {
    # 省略

    satisfy any;
    allow 127.0.0.1;
    deny all;
    auth_basic "basic authentication";
    auth_basic_user_file "/home/kusanagi/.htpasswd";
}

ここでのポイントは satisfy any を指定することで、特定のIP (上記の例では 127.0.0.1) に対しては Basic 認証を無効にしていることです。

さて、ここで誰からもアクセスされてはいけないファイルがあったとします。
例えば . で始まるファイルは一般的にアクセスさせない設定にしています。

上記の例に location を追加してみます。
location ~* /\. で正規表現により . から始まるパスをアクセスさせないようにしてみましょう。

server {
    # 省略

    satisfy any;
    allow 127.0.0.1;
    deny all;
    auth_basic "basic authentication";
    auth_basic_user_file "/home/kusanagi/.htpasswd";

    location ~* /\. {
        deny all;
    }
}

しかし、上記の設定のままでは .htaccess にアクセスできるケースが出てしまいます。
なぜかというと、location の外で宣言された4行目の satisfy anylocation 内でも有効になるからです。
satisfy のデフォルト値は all ですが、 any となっているので、 いずれかの条件 を満たすことでアクセスできてしまうのです。
この例で言えば、以下のいずれかの条件を満たすと . から始まるファイルをアクセスできてしまいます。

  1. 127.0.0.1 からのアクセス
  2. Basic 認証を通過する

このように どのような場合であっても アクセスできないような location を作りたい場合には、location の外で宣言された satisfy に影響しないように、明示的に satisfy all を入れることが必要です。

server {
    # 省略

    satisfy any;
    allow 127.0.0.1;
    deny all;
    auth_basic "basic authentication";
    auth_basic_user_file "/home/kusanagi/.htpasswd";

    location ~* /\. {
        satisfy all;  # ここで 4行目の satisfy any を上書き
        deny all;
    }
}

なお、これはIPアドレスでアクセス制限を行う場合でも同様です。

server {
    # 省略

    satisfy any;
    allow 127.0.0.1;
    allow 192.168.0.0/24;
    allow 10.0.1.0/24;
    deny all;

    location ~* /\. {
        satisfy all;  # ここで 4行目の satisfy any を上書き
        deny all;
    }
}

特定の location にアクセス制限をかける際の落とし穴

Basic認証をかけても PHP にはBasic認証がかからない場合がある

次に location で特定のディレクトリ配下にのみ Basic 認証をかけた場合を考えてみましょう。

server {
    # 省略

    location /auth_user_only {
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";
    }
}

これだけの状態であれば /auth_user_only に対して Basic 認証が効いています。

ところが、以下の場合だとどうでしょうか。

server {
    # 省略

    location /auth_user_only {
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";
    }

    location ~ [^/]\.php(/|$) {
        # 省略

        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;

        # 省略
    }
}

この場合 /auth_user_only/index.php には Basic 認証なしでアクセスできてしまいます。

なぜなら location の適用の優先順位 で紹介したように、
modifierなしの location /auth_user_only よりも 正規表現の location ~ [^/]\.php(/|$) が優先されるからです。

よって、制限をかけたい location は、この場合では正規表現で定義する必要があります。
また、その location の配下で PHP を実行する必要がある場合は、PHPの location をその中で再定義ことが必要です。

server {
    # 省略

    location ~ ^/auth_user_only {  # 正規表現に変更
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";

        location ~ [^/]\.php(/|$) {  # PHPのlocationを再定義
            # 省略

            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;

            # 省略
        }
    }

    location ~ [^/]\.php(/|$) {
        # 省略

        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;

        # 省略
    }
}

KUSANAGIでプロビジョンしたWordPress用のプロファイル設定を見ると、管理画面用のURLにアクセス制限をかけるために、管理画面用の PHP と普通の PHP で PHP の location を2つ定義しているのが分かります。

Basic認証をかけても画像などにはBasic認証がかからない場合がある

また、他に KUSANAGI では画像やCSS/JavaScriptといったリソースファイルをアクセスログに出力させないための location を定義しています。

server {
    # 省略

    location /auth_user_only {
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";
    }

    location ~* \.(jpg|jpeg|gif|png|webp|css|js|swf|ico|pdf|svg|eot|ttf|woff|woff2)$ {
        # 省略

        access_log off;
    }
}

上記の PHP の場合と同様に、例えば /auth_user_only/secret.jpg に対しては Basic 認証なしでアクセスできてしまいます。
この場合も、画像を許可させないようにするには、 location /auth_user_only 内に再定義が必要です。

server {
    # 省略

    location ~ ^/auth_user_only {  # 正規表現に変更
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";

        location ~* \.(jpg|jpeg|gif|png|webp|css|js|swf|ico|pdf|svg|eot|ttf|woff|woff2)$ {
            # 省略

            access_log off;
        }
    }

    location ~* \.(jpg|jpeg|gif|png|webp|css|js|swf|ico|pdf|svg|eot|ttf|woff|woff2)$ {
        # 省略

        access_log off;
    }
}

完全にBasic認証をかけたいならば…

location には正規表現を適用させない一致 ^~ があります。
これを利用すると、この location には他の正規表現が適用されないので、安全に Basic 認証をかけることができます。

server {
    # 省略

    location ^~ /auth_user_only {  # 配下に正規表現を適用させないmodifier
        satisfy any;
        allow 127.0.0.1;
        deny all;
        auth_basic "basic authentication";
        auth_basic_user_file "/home/kusanagi/.htpasswd";
    }
}

ただ、この場合は配下のディレクトリで PHP を実行することもできないので注意してください。
この配下の画像やリソースのアクセスログも出力されるようになります。

まとめ

昨今は「公開する意図がないデータが設定ミスによって公開されていた」といったセキュリティ事案が多く見られるようになりました。
Nginxの location は柔軟な一方で様々な location の記述がからみ合っていることで、設定ミスが起こりやすくなっています。

実運用に入る前には、必ず意図した通りのアクセス許可・不許可が設定できているか、きちんと確認するようにしてください。

また、その際にどこの設定が最終的に効いているのかを判断する際に、この説明を活用してください。

参考
Module nginx_http_core_module, location directive
Module nginx_http_auth_basic_module, auth_basic directive
Module nginx_http_core_module, satisfy directive

<< KUSANAGIコマンドとシェルスクリプトを組み合わせて運用するKUSANAGI で使える保守向けコマンド(iotop編) >>

関連記事

Webサイト運用の課題解決事例100選 プレゼント

Webサイト運用の課題を弊社プロダクトで解決したお客様にインタビュー取材を行い、100の事例を108ページに及ぶ事例集としてまとめました。

・100事例のWebサイト運用の課題と解決手法、解決後の直接、間接的効果がわかる

・情報通信、 IT、金融、メディア、官公庁、学校などの業種ごとに事例を確認できる

・特集では1社の事例を3ページに渡り背景からシステム構成まで詳解