checkip.amazonaws.com のレスポンスはX-Forwarded-For ヘッダーがあればX-Forwarded-For ヘッダーのIPアドレスになる

プロキシ経由で checkip.amazonaws.com にアクセスする際は注意
2022.08.31

checkip.amazonaws.com にアクセスするとプライベートIPアドレスが返ってきた

こんにちは、のんピ(@non____97)です。

皆さんは checkip.amazonaws.com を使っていますか? 私は使っています。

アクセスした元のIPアドレスを返してくれるとっても便利なサービスです。

同僚から「checkip.amazonaws.comにアクセスしたらプライベートIPアドレスが返ってくるんだが」と相談がありました。

checkip.amazonaws.com はVPCエンドポイントなど無いので表示されるIPアドレスはパブリックIPアドレスのみかと思っていましたが、どうやらそうではないようです。

ということで、この事象について調べてみたので紹介します。

いきなりまとめ

  • checkip.amazonaws.com はX-Forwarded-For ヘッダーがあればそこに記載のIPアドレスをレスポンスとして返す
  • 複数のIPアドレスをX-Forwarded-For ヘッダーに指定した場合最初のIPアドレスのみ返ってくる

検証の構成

検証の環境は以下の通りです。

構成図

同僚に聞いてみるとプロキシ経由でアクセスしたとのことだったので、ほぼネタバレですがプロキシサーバー(Squid)を立てます。

プロキシサーバーを経由する/しないでcheckip.amazonaws.comのレスポンスのIPアドレスがどのように変わるのか確認します。

検証環境はAWS CDKでデプロイします。

使用したAWS CDKのコードは以下リポジトリに保存しています。

checkip.amazonaws.com にアクセスしてみる

それでは、プロキシサーバーを経由する/しないでcheckip.amazonaws.comのレスポンスのIPアドレスがどのように変わるのか確認してみます。

まず、各EC2インスタンスのプライベートIPアドレスとパブリックIPアドレスをAWS CLIで確認しておきます。

$ aws ec2 describe-instances \
    --filters Name=vpc-id,Values=vpc-059fc834bc78d9464 \
    --query 'Reservations[*].Instances[*].{Name:Tags[?Key==`Name`].Value, PrivateIpAddress:NetworkInterfaces[*].PrivateIpAddress, PublicIpAddress:NetworkInterfaces[*].Association.PublicIp}'
[
    [
        {
            "Name": [
                "SquidStack/Client EC2 Instance"
            ],
            "PrivateIpAddress": [
                "10.0.0.13"
            ],
            "PublicIpAddress": [
                "18.208.230.58"
            ]
        }
    ],
    [
        {
            "Name": [
                "SquidStack/Squid EC2 Instance"
            ],
            "PrivateIpAddress": [
                "10.0.0.11"
            ],
            "PublicIpAddress": [
                "54.90.147.9"
            ]
        }
    ]
]

Client EC2 Instanceから直接 checkip.amazonaws.com にアクセスします。

$ curl http://checkip.amazonaws.com/
18.208.230.58

Client EC2 InstanceのグローバルIPアドレスである18.208.230.58が返ってきました。

それでは、プロキシサーバーを経由して checkip.amazonaws.com にアクセスします。

$ curl http://checkip.amazonaws.com/ \
    -x http://ip-10-0-0-11.ec2.internal:8080
10.0.0.13

なんということでしょう。Client EC2 InstanceのプライベートIPアドレスである10.0.0.13が返ってきました。

Squid EC2 Instanceからも checkip.amazonaws.com にアクセスしてみます。

# プロキシサーバーを経由せずにアクセス
$ curl http://checkip.amazonaws.com/
54.90.147.9

# プロキシサーバーを経由してアクセス
$ curl http://checkip.amazonaws.com/ \
    -x http://ip-10-0-0-11.ec2.internal:8080
10.0.0.11

# プロキシサーバーとしてlocalhostを指定してアクセス
$ curl http://checkip.amazonaws.com/ \
    -x http://localhost:8080
127.0.0.1

プロキシサーバーを経由しない場合は、Squid EC2 InstanceのパブリックIPアドレスが返ってきましたが、やはりプロキシサーバーを経由するとプライベートIPアドレスが返ってきます。

プロキシサーバーの指定をhttp://localhost:8080した場合に至ってはループバックアドレスです。

X-Forwarded-For ヘッダーを付与しないように設定を変えてみる

転送元のIPアドレスを転送先に伝えたいときに使うと言えば、X-Forwarded-For ヘッダーですよね。

Squidのデフォルトでは転送時にX-Forwarded-For ヘッダーにアクセス元のIPアドレスを追加します。もしかしたら、それの影響かもしれません。

ということで、Squidの設定変更をして、転送時にX-Forwarded-For ヘッダーを送信しないようにします。

# 現在のSquidの設定ファイルを確認
$ sudo cat /etc/squid/squid.conf
#
# Recommended minimum configuration:
#

# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 10.0.0.0/24

acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT

#
# Recommended minimum Access Permission configuration:
#
# Deny requests to certain unsafe ports
http_access deny !Safe_ports

# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports

# Only allow cachemgr access from localhost
http_access allow localhost manager
http_access deny manager

# We strongly recommend the following be uncommented to protect innocent
# web applications running on the proxy server who think the only
# one who can access services on "localhost" is a local user
#http_access deny to_localhost

#
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
#

# Example rule allowing access from your local networks.
# Adapt localnet in the ACL section to list your (internal) IP networks
# from where browsing should be allowed
http_access allow localnet
http_access allow localhost

# And finally deny all other access to this proxy
http_access deny all

# Squid normally listens to port 3128
http_port 0.0.0.0:8080

# Uncomment and adjust the following to add a disk cache directory.
#cache_dir ufs /var/spool/squid 100 16 256

# Leave coredumps in the first cache dir
coredump_dir /var/spool/squid

#
# Add any of your own refresh_pattern entries above these.
#
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern .               0       20%     4320
#  Don't display the version on the error page.
httpd_suppress_version_string on

# Anonymize hostnames
visible_hostname unknown

# Setting log format to Apache combined
logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log /var/log/squid/access.log combined

# 転送時にX-Forwarded-For ヘッダーを付与しないように設定を変更
$ sudo vi /etc/squid/squid.conf

# 変更箇所を確認
$ sudo tail /etc/squid/squid.conf

# Anonymize hostnames
visible_hostname unknown

# Setting log format to Apache combined
logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log /var/log/squid/access.log combined

forwarded_for off
request_header_access X-Forwarded-For deny all

# 変更内容に問題がないことを確認
$ sudo squid -k parse
2022/08/31 07:40:41| Startup: Initializing Authentication Schemes ...
2022/08/31 07:40:41| Startup: Initialized Authentication Scheme 'basic'
2022/08/31 07:40:41| Startup: Initialized Authentication Scheme 'digest'
2022/08/31 07:40:41| Startup: Initialized Authentication Scheme 'negotiate'
2022/08/31 07:40:41| Startup: Initialized Authentication Scheme 'ntlm'
2022/08/31 07:40:41| Startup: Initialized Authentication.
2022/08/31 07:40:41| Processing Configuration File: /etc/squid/squid.conf (depth 0)
2022/08/31 07:40:41| Processing: acl localnet src 10.0.0.0/24
2022/08/31 07:40:41| Processing: acl SSL_ports port 443
2022/08/31 07:40:41| Processing: acl Safe_ports port 80         # http
2022/08/31 07:40:41| Processing: acl Safe_ports port 21         # ftp
2022/08/31 07:40:41| Processing: acl Safe_ports port 443                # https
2022/08/31 07:40:41| Processing: acl Safe_ports port 70         # gopher
2022/08/31 07:40:41| Processing: acl Safe_ports port 210                # wais
2022/08/31 07:40:41| Processing: acl Safe_ports port 1025-65535 # unregistered ports
2022/08/31 07:40:41| Processing: acl Safe_ports port 280                # http-mgmt
2022/08/31 07:40:41| Processing: acl Safe_ports port 488                # gss-http
2022/08/31 07:40:41| Processing: acl Safe_ports port 591                # filemaker
2022/08/31 07:40:41| Processing: acl Safe_ports port 777                # multiling http
2022/08/31 07:40:41| Processing: acl CONNECT method CONNECT
2022/08/31 07:40:41| Processing: http_access deny !Safe_ports
2022/08/31 07:40:41| Processing: http_access deny CONNECT !SSL_ports
2022/08/31 07:40:41| Processing: http_access allow localhost manager
2022/08/31 07:40:41| Processing: http_access deny manager
2022/08/31 07:40:41| Processing: http_access allow localnet
2022/08/31 07:40:41| Processing: http_access allow localhost
2022/08/31 07:40:41| Processing: http_access deny all
2022/08/31 07:40:41| Processing: http_port 0.0.0.0:8080
2022/08/31 07:40:41| Processing: coredump_dir /var/spool/squid
2022/08/31 07:40:41| Processing: refresh_pattern ^ftp:          1440    20%     10080
2022/08/31 07:40:41| Processing: refresh_pattern ^gopher:       1440    0%      1440
2022/08/31 07:40:41| Processing: refresh_pattern -i (/cgi-bin/|\?) 0    0%      0
2022/08/31 07:40:41| Processing: refresh_pattern .              0       20%     4320
2022/08/31 07:40:41| Processing: httpd_suppress_version_string on
2022/08/31 07:40:41| Processing: visible_hostname unknown
2022/08/31 07:40:41| Processing: logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
2022/08/31 07:40:41| Processing: access_log /var/log/squid/access.log combined
2022/08/31 07:40:41| Processing: forwarded_for off
2022/08/31 07:40:41| Processing: request_header_access X-Forwarded-For deny all
2022/08/31 07:40:41| Initializing https proxy context

# 設定をリロード
$ sudo systemctl reload squid

この状態でClient EC2 Instanceから checkip.amazonaws.com にアクセスしてみます。

# プロキシサーバーを経由せずにアクセス
$ curl http://checkip.amazonaws.com/
18.208.230.58

# プロキシサーバーを経由してアクセス
$ curl http://checkip.amazonaws.com/ \
    -x http://ip-10-0-0-11.ec2.internal:8080
54.90.147.9

プロキシサーバーを経由した場合は、Squid EC2 InstanceのグローバルIPアドレスが返ってきました。

Squid EC2 Instanceからも試してみます。

# プロキシサーバーを経由せずにアクセス
$ curl http://checkip.amazonaws.com/
54.90.147.9

# プロキシサーバーを経由してアクセス
$ curl http://checkip.amazonaws.com/ \
    -x http://ip-10-0-0-11.ec2.internal:8080
54.90.147.9

# プロキシサーバーとしてlocalhostを指定してアクセス
$ curl http://checkip.amazonaws.com/ \
    -x http://localhost:8080
54.90.147.9

全てSquid EC2 InstanceのグローバルIPアドレスが返ってきました。

どうやら、checkip.amazonaws.com はX-Forwarded-For ヘッダーがあればそこに記載のIPアドレスをレスポンスとして返すようですね。

めでたし。めでたし。

「「「X-Forwarded-For ヘッダーに適当なIPアドレスを追加した場合はどうなるのでしょう ??」」」

気になりますよね。確認してみました。

X-Forwarded-For ヘッダーにCloudflareが提供しているパブリックDNSサービスのIPアドレスである1.1.1.1を指定してみました。

$ curl http://checkip.amazonaws.com/ \
    -H "X-Forwarded-For: 1.1.1.1" \
    -v
*   Trying 34.200.207.31:80...
* Connected to checkip.amazonaws.com (34.200.207.31) port 80 (#0)
> GET / HTTP/1.1
> Host: checkip.amazonaws.com
> User-Agent: curl/7.79.1
> Accept: */*
> X-Forwarded-For: 1.1.1.1
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 31 Aug 2022 07:58:36 GMT
< Server: lighttpd/1.4.53
< Content-Length: 8
< Connection: keep-alive
<
1.1.1.1
* Connection #0 to host checkip.amazonaws.com left intact

はい、1.1.1.1が返ってきました。

X-Forwarded-For ヘッダーに複数のIPアドレスを指定した場合はどのようなレスポンスが返ってくるかも確認してみましょう。

$ curl http://checkip.amazonaws.com/ \
    -H "X-Forwarded-For: 0.0.0.0" \
    -H "X-Forwarded-For: 255.255.255.255" \
    -v
*   Trying 3.217.248.28:80...
* Connected to checkip.amazonaws.com (3.217.248.28) port 80 (#0)
> GET / HTTP/1.1
> Host: checkip.amazonaws.com
> User-Agent: curl/7.79.1
> Accept: */*
> X-Forwarded-For: 0.0.0.0
> X-Forwarded-For: 255.255.255.255
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 31 Aug 2022 08:00:44 GMT
< Server: lighttpd/1.4.53
< Content-Length: 8
< Connection: keep-alive
<
0.0.0.0
* Connection #0 to host checkip.amazonaws.com left intact

0.0.0.0255.255.255.255を指定しましたが、最初の0.0.0.0しか返ってきませんでした。

他のパターンも試してみましたが、どうやら最初のIPアドレスしか返さないようです。

プロキシ経由で checkip.amazonaws.com にアクセスする際は注意

checkip.amazonaws.com のレスポンスはX-Forwarded-For ヘッダーがあればX-Forwarded-For ヘッダーのIPアドレスになることを確認しました。

プロキシ経由でcheckip.amazonaws.com にアクセスする際は注意が必要そうですね。

プライベートIPアドレスが返ってきたら、プロキシの設定を確認してみると良いと思います。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!