LoginSignup
23
5

More than 3 years have passed since last update.

nginx、怖いリバースプロキシの話(正規表現のキャプチャがURIデコードされて困った件)

Last updated at Posted at 2019-10-18

自分への備忘録として。

http://example.com/api/hogehoge?foo=bar
にアクセスされたら、
http://example.com:8000/hogehoge?foo=bar
という形でリバースプロキシさせたいとき、ついついこんな風に書きたくなります。

location ~ ^/api/(.*)$ {
    proxy_pass http://example.com:8000/$1$is_args$args;
}

が、これは間違い。下のように書くべき。

location ~ ^/api/.*$ {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://localhost:8000;
}

どういうことかというと、nginxのlocationディレクティブにおける正規表現のキャプチャは、uriデコードされたものがキャプチャされてしまうのです。

なので、もし
http://example.com/api/hoge%20hoge
こういうURIエンコード済みのURIだった場合、
"http://example.com:8000/hoge hoge"
こんな空白入りのURIになってしまい、502 Bad Requestとかで死にます。

それを阻止するには、デコード前の状態をキャプチャしないとダメで、それを行うにはrewriteディレクティブを使います。

これは、公式ドキュメントにも書いてあります。
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive:
(中略)
When the URI is changed inside a proxied location using the rewrite directive, and this same configuration will be used to process a request (break):

location /name/ {
rewrite /name/([^/]+) /users?name=$1 break;
proxy_pass http://127.0.0.1;
}

In this case, the URI specified in the directive is ignored and the full changed request URI is passed to the server.

しかも、rewriteに後続する、proxy_passディレクティブには変数とかは使用してはいけません。余計なものを付けなければ、変更された完全なリクエストURIがサーバーに渡されます。

あー怖かった。

23
5
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
23
5