Apacheリバースプロキシにおけるスラッシュと%2F問題はまったポイント

Apacheと%2Fでを調べると沢山出てくる内容なのですが、ApacheはURLから取得されるPATH_INFO変数に /エンコードである %2F があると404を返します。

これ自体はAllowEncodedSlashesというパラメータをOnにしてやると処理できるようになります。

<VirtalHost>
...
AllowEncodedSlashes on
...
</VirtalHost>
  • AllowEncodedSlashes off %2F は404エラー
  • AllowEncodedSlashes on %2F/ にデコード

これですが、デコードしないで後ろのWEBサーバに渡して欲しいという要件に合う選択肢がありません。これが一つのハマりポイントでした。

今書いている時点では日本語のマニュアルにはこの二つのオプションまでしか書かれていないのですが、英語にマニュアル見るともう一つ NoDecode というオプションがあります。

AllowEncodedSlashes On|Off|NoDecode http://httpd.apache.org/docs/2.4/en/mod/core.html#allowencodedslashes

これを使うことで %2F がデコードされないで後ろの処理に渡されます。この3パターンが設定可能です。

static const char *set_allow2f(cmd_parms *cmd, void *d_, const char *arg)
{
    core_dir_config *d = d_;

    if (0 == strcasecmp(arg, "on")) {
        d->allow_encoded_slashes = 1;
        d->decode_encoded_slashes = 1; /* for compatibility with 2.0 & 2.2 */
    } else if (0 == strcasecmp(arg, "off")) {
        d->allow_encoded_slashes = 0;
        d->decode_encoded_slashes = 0;
    } else if (0 == strcasecmp(arg, "nodecode")) {
        d->allow_encoded_slashes = 1;
        d->decode_encoded_slashes = 0;
    } else {
        return apr_pstrcat(cmd->pool,
                           cmd->cmd->name, " must be On, Off, or NoDecode",
                           NULL);
    }
    return NULL;
}

https://github.com/apache/httpd/blob/2.4.29/server/core.c#L3066-L3085

これで解決かと思ったのですが今度は %252F という文字が渡されるようになりました。

これは %2F を更にエンコードしたものになります。せっかく NoDecode オプションつけて生データを渡そうとしているので、誰がエンコードしてるんだと調べたところ、 mod_proxyエンコードされているということがわかりました。これがもう一つのハマりポイントです。

apache2 - Need to allow encoded slashes on Apache - Stack Overflow

回避するには参考URLにあるように以下のように設定します。

ProxyPass http://backendserver:8080/example/ nocanon

マニュアルにも以下のように記載があります。

Normally, mod_proxy will canonicalise ProxyPassed URLs. But this may be incompatible with some backends, particularly those that make use of PATH_INFO. The optional nocanon keyword suppresses this and passes the URL path "raw" to the backend. https://httpd.apache.org/docs/current/en/mod/mod_proxy.html#proxypass

手元の環境では ProxyPass ではなくRewriteRule ディレクティブの P フラグでProxyPassと同等のことを実装していたので、そちらにも同じもの無いか探したら NE フラグというのがあり、こちらを利用すれば同じこと実現できます。

RewriteRule ^(.*) http://backendserver:8080/example/$1 [P,NE]

RewriteRule Flags - Apache HTTP Server Version 2.4