CloudFrontのデフォルトルートオブジェクトを設定することで意図しないコンテンツの公開が防げるケースを実際に確認してみた

CloudFrontではデフォルトルートオブジェクトを設定することがベストプラクティスにより推奨されています。この設定により意図しない情報の漏洩が防げるケースがあることを、S3バケット内オブジェクトの一覧の不意な公開を例に確認してみました。
2022.04.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。AWS Security Hubの「AWS 基礎セキュリティのベストプラクティス v1.0.0」にはCloudFrontディストリビューションでデフォルトのルートオブジェクトが設定されているかをチェックするコントロールがあります。個人的にはこのCloudFrontディストリビューションのデフォルトルートオブジェクト、あまり設定したことがありません。そのためSecurity Hubのセキュリティチェックでも多くがFAILEDとなってしまった状態です。

デフォルトルートオブジェクトが必要な場合には、いわゆる「インデックスドキュメント」(ルートのみではなく各ディレクトリで/へのアクセスを補完)も必要になり、S3の静的サイトホスティングやLambda@Edge、CloudFront Functionsなどの利用を考えていました。そんなことでCloudFrontのデフォルトルートオブジェクトの設定、セキュリティチェック項目として重要度がCRITICALではあるものの、設定内容の重要性にいまいちピンと来ていませんでした。

本エントリでこのCloudFrontのデフォルトルートオブジェクトを設定しなかった場合に発生するリクスの一例と、それがデフォルトルートオブジェクト設定を行うことで回避できる点を具体的に確認してみます。

AWS基礎セキュリティのベストプラクティスのCloudFront.1

まずはSecurity Hubの「AWS 基礎セキュリティのベストプラクティス v1.0.0」での該当するチェック項目をざっと確認しておきましょう。チェック項目(コントロール)に振られたIDは[CluodFront.1]、重要度はCRITICALです。CloudFrontのリソースに対するチェックとなるので、バージニア北部(us-east-1)リージョンでのみチェックが行われます。(ただしSecurity Hubのリージョン集約を設定していれば他のリージョンでもチェック自体は可能になります。)

Security Hubのユーザガイドでコントロールの内容をさらに確認してみましょう。失敗となる条件はシンプルに、対象となるCloudFrontディストリビューションにデフォルトルートオブジェクトが設定されているか否か、ということですね。

AWS の基本的なセキュリティのベストプラクティスコントロール - AWS Security Hub

なお「AWS 基礎セキュリティのベストプラクティス v1.0.0」のコントロールとして2021年3月に追加されたものとなります。

上記ブログエントリに記載がありますが、一例としてオリジンがS3であった場合のバケットポリシーの設定ミスによる意図しない情報公開を防ぐことが、デフォルトルートオブジェクトの指定で可能になります。この「バケットポリシーの設定ミスによる意図しない情報公開」、実際にはS3に対するオブジェクト一覧の取得(ListObject)操作が該当します。以下、実際にどうのようなかたちでこのオブジェクト一覧が公開されてしまうのかを確認してみましょう。

S3バケット内オブジェクトの一覧の公開は意外に?簡単にできる

S3を使ってコンテンツを公開するケースを考えてみます。公開用S3バケットということで、Block public accessはオフとします。バケット内のすべてのコンテンツを公開する前提で、以下のようなバケットポリシーを記載しているとしましょう。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-bucket/*"
        }
    ]
}

格納しているファイルは以下のような具合です。公開用途のファイルのほか、公開を意図していないファイルも何らかの事情で格納している、というシチュエーションとしましょう。(ファイル名で想像してみてください。)実際にはファイル名を複雑にしておきそのファイルのURL(パス)を公開しなければファイルへはアクセスできないだろう、と考えるケースですね。ファイルのほか公開を意図しないフォルダもあったりするとします。(後述しますが、厳密にはS3ではフォルダというものは幻想です。)

この状態で、公開用のファイル「public-file1.txt」などのURL、https://my-bucket.s3.ap-northeast-1.amazonaws.com/public-file-1.txtを公開してファイルをホスティングしているぶんには、たしかに公開を意図していないファイルのファイル名(パス)が漏れない限り、ファイルにアクセスされることはないでしょう。

そしてこの状態でドメインのルート、https://my-bucket.s3.ap-northeast-1.amazonaws.com/にアクセスしても以下のように403 Forbiddenが返ってくるだけです。

% curl -i https://my-bucket.s3.ap-northeast-1.amazonaws.com/
HTTP/1.1 403 Forbidden
x-amz-bucket-region: ap-northeast-1
x-amz-request-id: T54JXFFXPHMSB1Y5
x-amz-id-2: l8SBm+MizpAI7UAaRQx6Hq6HevXKBzdv+Txf3Vbb4u5XGcA7DKvJ6/Al5Q8tfmTeY9VLID+FcSA=
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Fri, 29 Apr 2022 11:21:43 GMT
Server: AmazonS3

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>T54JXFFXPHMSB1Y5</RequestId><HostId>l8SBm+MizpAI7UAaRQx6Hq6HevXKBzdv+Txf3Vbb4u5XGcA7DKvJ6/Al5Q8tfmTeY9VLID+FcSA=</HostId></Error>%

これはS3のバケットポリシーでs3:GetObjectのみをパブリックに公開しているからですね。意図した公開設定がなされているといえます。

さてここで、S3のバケットポリシーを以下のように変えてしまうとどうでしょうか。これまでのs3:GetObjectに加えてs3:ListBucketを追加したかたちですね。対象リソースにはs3:ListBucketで必要となるS3バケット自体も含めてしまっています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket/*",
                "arn:aws:s3:::my-bucket"
            ]
        }
    ]
}

例えばですが、Apache(Apache HTTP Server)のDirectoryIndexディレクティブのように指定したディレクトリ(フォルダ)配下のファイル一覧を表示させたいがための試行錯誤の一環として、このようなバケットポリシーを設定してしまうことはありうるのかな、と思います。

このようなs3:ListBucketを追加して無制限に公開してしまった場合、S3のドメインのルートなどにアクセスすると、いとも簡単に(?)S3バケット内のオブジェクトの一覧が取得できます。

この一覧情報(ListBucketの結果)にはS3バケット名のほかに、いわゆる「フォルダ」配下の内容もリストアップされている点にも留意しましょう。これはS3の基盤技術が単純なKVS(Ke-Value型データストア)であり、フォルダ+ファイル名部分は単にKeyとして扱われているからですね。(参考: Amazon S3における「フォルダ」という幻想をぶち壊し、その実体を明らかにする | DevelopersIO

CloudFrontのデフォルトルートオブジェクトを設定してS3バケット内オブジェクト一覧の意図しない公開を防ぐ

S3のバケットポリシーの変更によって、意外にも?簡単ににS3バケット内のオブジェクトの一覧の公開ができてしまうことを確認しました。ここでCloudFrontのディストリビューション設定、デフォルトのルートオブジェクトの指定の効果について確認してみます。

さきほどのS3自体のURLを公開して、というシチュエーションを想定していましたが、このS3にCloudFrontをかぶせて公開していると想定しましょう。公開ドメインについても独自ドメインを使用できたり、CloudFront自体のドメインを使う場合でもS3バケット名を秘匿する、という効果もありますね。(そのほか、そもそもCloudFrontでOAIを設定するべきでもありますが、いったんここではおいておきましょう。)

S3をオリジンとしてCloudFrontを構成した場合でも、さきほどと同じように誤ってs3:ListBucket権限を無制限にく許可してしまった場合、ルートオブジェクト/にアクセスするとオブジェクトの一覧情報が取得できてしまいます。取得できる内容はS3に直接アクセスした場合と同様なものなので、S3のバケット名も含まれてしまっていますね。(せっかくCloudFront利用でバケット名が秘匿できていたのに、、。)

ここでデフォルトルートオブジェクトを有効にしておけば、S3バケット内の一覧の公開が防げるというわけです。

実際にデフォルトルートオブジェクトを設定後、先ほどと同様にルートオブジェクト/にアクセスしてみます。(S3側のバケットポリシーは変更していません。)デフォルトルートオブジェクトに指定したファイルの中身がレスポンスとして返ってきました。(Chromeブラウザではファイル内容がそのままダウロードされてしまったため、curlコマンドでの確認です。)

% curl -i https://dxxxxxxxxxxxx.cloudfront.net/
HTTP/2 200
content-type: binary/octet-stream
content-length: 10
date: Fri, 29 Apr 2022 16:38:49 GMT
last-modified: Fri, 29 Apr 2022 11:15:31 GMT
etag: "a0a6e1a375117c58d77221f10c5ce12e"
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 bd9e75a01c94b1e728afae7d6a1a6db4.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P4
x-amz-cf-id: rEISoucCw6m429LdYdyul4g6VvYua74t80zCRb5t7cWK97cmQTcP2w==

foobarbaz

まとめ

Amazon CloudFrontディストリビューション設定のデフォルトルートオブジェクトを設定しておくことで、意図しない情報漏洩のリスクを低減できることを、実際にS3で意図しないバケット内のオブジェクト一覧の公開が起こり得るケースを設定しつつ確認してみました。

このAmazon CloudFrontのデフォルトルートオブジェクト設定、S3をオリジンとするケースではS3の静的Webサイトホスティングを無効にしている状態だと階層構造のないWebページにぐらいしか設定する機会がなく(いわゆるインデックスドキュメントとしては利用できないため)、あまり使いみちがないなぁ、ぐらいにしか考えていませんでした。しかし「AWS基礎セキュリティのベストプラクティス」で重要項目として挙げられるように、たしかに意図しない情報流出を防ぐために設定しておく、という利用目的があることがわかりました。

CloudFrontディストリビューションで使用しているドメイン自体をWebサイトとしてはではなく画像などコンテンツの配信のみに使用し、index.htmlのようないわゆるインデックスをルートオブジェクトとして返さなくてもいいようなケースでも、今回のような意図しない情報漏洩などのリスク排除のため、デフォルトルートオブジェクトを設定しておくのが良いのだなと思いました。なおすでに稼働中のディストリビューションに対して設定する場合は、オリジン側やCloudFront Functions、Lambda@Edgeなどで別途インデックスドキュメントを補完する仕組みが入っていないか、もしそのような仕組みが入っている場合は動作とバッティングが起こらないか、確認してから設定するようにしましょう。