妄想まとめ

研究とかWebセキュとか時事ネタとか。 @kazu1130_h

EC2上のAWS CLIで使われている169.254について

お久しぶりです。ひろたんです。

前々から気になっていたAWS EC2の169.254.169.254について少し遊んでみたのでまとめます。


EC2では、インスタンス内から http://169.254.169.254/ にアクセスすると、そのインスタンスに関する情報が取得できるようになっています。

docs.aws.amazon.com

あまり意識したことはないかもしれませんが、インスタンスにIAMロールを結び付けた状態でAWS CLIを使うと内部的にこの169のURLが叩かれる仕組みになっています。
これはインスタンス内で--debugオプションを使ってAWS CLIを実行するとわかると思います。

[ec2-user@ip-172-31-30-197 ~]$ aws s3 ls --debug
~~~
2018-09-03 11:11:33,898 - MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - INFO - Starting new HTTP connection (1): 169.254.169.254
2018-09-03 11:11:33,899 - MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "GET /latest/meta-data/iam/security-credentials/ HTTP/1.1" 200 8
2018-09-03 11:11:33,901 - MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - INFO - Starting new HTTP connection (1): 169.254.169.254
2018-09-03 11:11:33,902 - MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "GET /latest/meta-data/iam/security-credentials/testrole HTTP/1.1" 200 898
2018-09-03 11:11:33,902 - MainThread - botocore.credentials - DEBUG - Found credentials from IAM Role: testrole
2018-09-03 11:11:33,903 - MainThread - botocore.loaders - DEBUG - Loading JSON file: /usr/lib/python2.7/dist-packages/botocore/data/endpoints.json
2018-09-03 11:11:33,919 - MainThread - botocore.session - DEBUG - Loading variable profile from defaults.
~~~

これについては以下のAWS公式ヘルプに書かれていますね。
docs.aws.amazon.com

さて、ここで気になる警告が……。

警告

IAM ロールでインスタンスメタデータを使用するサービスを使用する場合は、サービスで HTTP 呼び出しが行われるときに認証情報を公開しないように注意する必要があります。認証情報を公開できるサービスの種類には、HTTP プロキシ、HTML/CSS 検証サービス、および XML インクルードをサポートする XML プロセッサーが含まれます。

この警告。色々妄想が膨らみそうですね。ブログタイトルも妄想まとめですしね!!


例えばこんなコードが動いていたとします。

GitHub - kazu1130/ssdemo: ScreenShot

これは送られてきたURLに飛んでスクショを撮って保存するだけのプログラムです。

例えばここに http://169.254.169.254/latest/meta-data/iam/security-credentials/ というURLを投げると…… ご想像の通り、credentialが奪えます。
f:id:kazu1130_h:20180903201728p:plain

あとは、盗んだcredentialで(exportコマンドと任意のAWS CLIのコマンドを)走り出すだけです!!!(?)

$ export AWS_ACCESS_KEY_ID=<Access-Key-as-in-Previous-Output>
$ export AWS_SECRET_ACCESS_KEY=<Secret-Access-Key-as-in-Previous-Output>
$ export AWS_SESSION_TOKEN=<Session-Token-as-in-Previous-Output>
$ aws s3 ls


今回のコードでは画像を./data/に保存しているだけなので何のロールも無い可能性がありますが、これが例えばS3にアップロードするようなコードだった場合、credentialが奪えそうな気がしませんか?
(因みにロールが無い場合は、/iam/security-credentials/というパス自体が404になります)


実際、サイトへアクセスして結果を返す機能を利用してcredentialを奪えた例がHackerOneに上がってたりします。
hackerone.com



さて、このような場合どういった対策を取ればよいのでしょうか。


受け取ったプログラム側で対策しても良いのですが、その場合ちゃんと接続先が169.254でないことを確認する必要があります。単に文字列で見るだけだと、例えば悪意のあるドメインを参照させてアクセスさせたり、IPアドレスの10進数表記を使ってアクセスさせたり、色々回避されてしまうからです。
※さっきのサンプルで試したら、ちゃんと http://2852039166/ を解釈してくれました。流石Chrome先生!


実際に組まなければいけなくなった場合、僕だったらおそらくサービスを丸ごとコンテナにぶち込んでiptablesで制限をかけると思います。
外部にアクセスしている機能(今回のならHeadless-Chrome)の詳細が分かっていれば、もしかしたらコードレベルで対処するかもしれませんが、解釈の違い等で死ぬ未来が見えているので出来る限りコードを書かない対処をしたくなりますよね。

上記対策だとコンテナ内からAWS CLIを叩けなくなってしまいますが、コンテナ内外でデータDirを共有しつつコンテナ外でアップロード用のスクリプトを動かすことで、コンテナ内から169につなぐ必要がなくなるため、丸ごとブロックしても正常にAWS CLIを動かすことができます。

具体的には

iptables -I DOCKER-USER -d 169.254.169.254 -j DROP

のようなコマンドを打てばブロックできるはずです。



まぁそもそもLambda等に切り出しちゃうのが一番なのかもしれませんがねw


以上、明日某所で話すネタを先にまとめた記事でした。