12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Hostヘッダフォージェリの対策について

Last updated at Posted at 2018-07-22

はじめに

この記事はnginxの設定ミスで起こるHostヘッダフォージェリの対策方法について検証した時のメモです。

$http_host$hostの両方でHostヘッダフォージェリが可能かどうかを調べます。
もし間違えている箇所がありましたら、指摘していただければ幸いです。

$http_host

リクエストラインに絶対URIを記述した場合

絶対URIに別のホストを指定すると、リクエストラインで指定されたホストにアクセスしますが、Hostヘッダはそのまま送信されます。

つまり、HOSTヘッダの値を改竄することでHostヘッダフォージェリが可能です。

リクエストラインに存在しないホストを指定した場合

nginxは、認識できないホスト名を持つリクエストを受信した場合

  • listenディレクティブにdefault_serverが設定されているサーバー
  • default_serverが設定されていない場合は、nginxが最初に読み込んだ設定のサーバー

にリクエストが送信されます。
例えば

#aaa.conf

server {
    server_name    aaa.example.com;
    listen 80;
}

location / {
            proxy_set_header Host $http_host;
            proxy_pass http://192.168.11.10/echo.php?a;
        }

# bbb.conf

server {
    server_name    bbb.example.com;
    listen 80;
}

location / {
            proxy_set_header Host $http_host;
            proxy_pass http://192.168.11.10/echo.php?b;
        }

のように、設定されていたとします。

この時、以下のようなリクエストを送信します。

GET https://hoge.example.com/ HTTP/1.1
Host: evil.com
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ja;q=0.8

アプリケーションサーバーには以下のようなアクセスが来ます

xxx.xxx.xxx.xxx - - [14/Jul/2018:23:28:49 +0900] "GET /echo.php?a HTTP/1.0" 200 34 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"

この場合ではdefault_serverを設定していない為、最初に読み込まれた設定のaaa.confのホストにリクエストが送信されています。

この時の $_SERVER["HTTP_HOST"]の値と $_SERVER["SERVER_NAME"]の値は以下の通りです

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 14 Jul 2018 14:49:23 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 34
Connection: close
X-Powered-By: PHP/7.0.30
Strict-Transport-Security: max-age=15768000

host:evil.com
server name:evil.com

Hostヘッダフォージェリに成功していることがわかります。

$host

$host は以下の優先順位でホストが決定します。

  1. リクエストライン
  2. Host リクエストヘッダの値
  3. リクエストに一致するサーバー名

リクエストラインに絶対URIを記述した場合

以下のようなリクエストを送信します

GET https://example.com/ HTTP/1.1
Host: evil.com
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ja;q=0.8

$hostはHostヘッダよりもリクエストラインを優先するので、アプリケーションに送信されるHostヘッダの値は example.com になります。

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 14 Jul 2018 15:43:57 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 54
Connection: close
X-Powered-By: PHP/7.0.30
Strict-Transport-Security: max-age=15768000

host:example.com
server name:example.com

しかし、存在しないホストをリクエストラインに記述したり、Hostヘッダを改竄することで同様に

  • listenディレクティブにdefault_serverが設定されているサーバー
  • default_serverが設定されていない場合は、nginxが最初に読み込んだ設定のサーバー

にリクエストを送信してしまいます。

  • リクエストラインに存在しないホストを記述した場合
GET https://evil.com/ HTTP/1.1
Host: example.com
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ja;q=0.8

レスポンス

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 14 Jul 2018 16:16:30 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 34
Connection: close
X-Powered-By: PHP/7.0.30
Strict-Transport-Security: max-age=15768000

host:evil.com
server name:evil.com
  • Hostヘッダを改竄した場合
GET /safe HTTP/1.1
Host: evil.com
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,ja;q=0.8

レスポンス

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 14 Jul 2018 16:20:11 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 34
Connection: close
X-Powered-By: PHP/7.0.30
Strict-Transport-Security: max-age=15768000

host:evil.com
server name:evil.com

## わかったこと
$http_hostの代わりに$hostを使用した場合でも
存在しないホストをリクエストラインに記述したり、Hostヘッダを改竄することで

  • listenディレクティブにdefault_serverが設定されているサーバー
  • default_serverが設定されていない場合は、nginxが最初に読み込んだ設定のサーバー

に偽装したホスト名を送信することが可能ということがわかりました。

対策

サーバーセキュリティ

それでも対策は簡単です。

  • $http_hostの代わりに$hostを使用してください。
  • 認識できないホスト名を持つ全てのリクエストをキャッチするための専用の仮想ホストを作成してください。

アプリケーションセキュリティ

管理者がどのホスト名を信頼しているかを完全に自動的に特定する方法はないため、この問題を解決するのは難しいです。最も安全な解決策ではありますが、アプリケーションの初期設定時に信頼できるドメインのホワイトリストを設定するというアプローチを使用することです。

PHPでは $_SERVER["SERVER_NAME"] について以下のように記述されています。

'SERVER_NAME'
現在のスクリプトが実行されているサーバーのホスト名です。 スクリプトがバーチャルホスト上で実行されている場合は そのバーチャルホスト名となります。

注意: Apache 2 では、UseCanonicalName = On と ServerName を設定する必要があります。 そうしなければ、この値はクライアントが提供するホスト名を指すようになってしまい、無意味です。 また、セキュリティ的な意味合いでこの値に頼ってはいけません。

nginxの場合は

fastcgi_param SERVER_NAME "example.com";

のようにして、SERVER_NAMEを指定することで利用できますが、これは環境依存になるのでオススメできません。まだ定数として保持してそれを利用した方が良いと考えます。

参考記事

対策方法は以下のリンクの一部抄訳です。
https://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html

12
13
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
12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?