AWS特化のCTFが超楽しかった!【Security-JAWS / writeup】
先日、AWSのセキュリティに関するコミュニティ「Security-JAWS」にて、AWS環境に特化したCTFが開催されました。
これが超超超楽しくて、学びにもなったとてもいいイベントだったので、少し時間が経ってしまいましたが、writeup兼ふりかえり記を残します。
ちなみに私はCTF初挑戦でAWSも入門書を1周したくらいの知識しかありません。しかし問題の構成がよく練られていたので、私のような初心者でも時間いっぱいじっくりと楽しむことができました(GPTを駆使しつつですが)。
CTFの環境構築に作問に、と、これを準備するのは相当大変だったと思うのですが・・・運営の方々には本当に感謝です!
問題はTrivia、Warmup、Easy、Medium、Hardの5ランクに分かれていて、私はTrivia、Warmup、Easyランクの問題に挑戦しました。
緑色が正解した問題です
TriviaはAWSの知識を問うクイズのような形式で、調べたらわかるものだったので、ここではWarmup、Easyの問題で印象に残ったものをまとめます!
事前準備
- M2 Macbook Air
- AWS CLIのインストール
- Homebrew経由でインストールしました。
https://zenn.dev/akakuro/articles/30f570b8863bef
- Homebrew経由でインストールしました。
Warmup
AWS CLI practice
指示:このIAMユーザーが所属するAWSアカウントIDは何?
与えられたデータ:IAMユーザーのアクセスキーIDとシークレットアクセスキー
※アカウントのリージョンは全問題共通で事前に知らされています。
IAMユーザーとは?
AWSアカウントで利用している各種サービスにアクセスできるユーザー。ユーザーやグループごとに権限をカスタマイズできる。
AWS CLIのaws configure
コマンドでアクセスキーID、シークレットアクセスキー、リージョンを設定し、aws sts get-celler-identity
コマンドでユーザーの識別情報を取得、という流れでアカウントIDがわかりました。
$ aws configure
AWS Access Key ID [None]: [アクセスキーID]
AWS Secret Access Key [None]: [シークレットアクセスキー]
Default region name [None]: us-east-1
Default output format [None]: json
$ aws sts get-celler-identity
{
"UserId": "AIDA5YYUV3PDXROBLRDKF",
"Account": "946546793415", #これがアカウントID
"Arn": "arn:aws:iam::946546793415:user/ctf_challenge_0"
}
なお、このあとは問題ごとにIAMユーザーを切り替えてAWS CLIを操作するようになっていました。
私は都度上書きしていたのですが、--profile
オプションを使えばプロファイルに名前をつけて保存することができたようです。
Find data2
指示:FLAGはバケツに突っ込んであるので探してね!
与えられたデータ:IAMユーザーのアクセスキーIDとシークレットアクセスキー
バケツとはAmazon S3のこと。ロゴがバケツですもんね!
この問題の前に、AWSコンソールからS3を操作してFLAGを入手する問題がありました。
この問題では、AWS CLIを操作してS3の中にあるFLAGを入手します。
まず、aws configure
で、AWS CLIにIAMユーザーを登録します。
aws s3 ls
コマンドでs3のディレクトリ構成を確認します。
$ aws s3 ls
2023-08-13 23:13:07 backup-37szjp8pny7xx01
2023-08-26 22:42:49 camouflagedrop-wxhqft4lqf-assets-wxhqft4lqf-assets
2023-08-26 22:38:56 camouflagedrop-wxhqft4lqf-web-wxhqft4lqf-static
2023-08-22 20:15:49 cdk-hnb659fds-assets-055450064556-ap-northeast-1
2023-08-25 03:05:46 file-storage-afeffefespntbaiw7o5
2023-08-06 21:55:58 himituno-bucket1
2023-08-06 21:58:32 himituno-bucket2
2023-08-06 23:08:45 himituno-bucket3
2023-08-27 02:36:31 my-backup-file-ulxmhiw3jroec7sclynr06fkvhqssf
2023-08-22 20:56:22 s3misssignurl-t6j4qj4r-assets-t6j4qj4r-assets-bucket
2023-08-22 20:52:05 s3misssignurl-t6j4qj4r-web-t6j4qj4r-static-host-bucket
2023-08-24 04:22:54 totemo-kawaii-neko-no-namae-ha-lise-desu
2023-08-27 01:18:58 ulxmhiw3jroec7sclynr06fkvhqssf
この問題の前の問題では、himituno-bucket-x
内にFLAGがあったので、今回もhimituno-bucket1``himituno-bucket2``himituno-bucket3
のどこかにFLAGがあるのではないかと予想します。
さらにaws s3 ls
コマンドで調べると、himituno-bucket2
にSECRET
フォルダがあることがわかりました。
$ aws s3 ls s3://himituno-bucket1
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
$ aws s3 ls s3://himituno-bucket2/
PRE SECRET/
$ aws s3 ls s3://himituno-bucket3/
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
SECRET
フォルダの中にFLAGがあるかな? と思って調べると・・・。
$ aws s3 ls s3://himituno-bucket2/SECRET/
PRE 1/
PRE 10/
PRE 100/
PRE 1000/
PRE 101/
PRE 102/
PRE 103/
PRE 104/
(略)
さらに1〜1000の孫フォルダが入っていました!
この中のどこにFLAGが入っているのでしょうか。
手作業で調べるわけにはいかないので、GPTにスクリプトを書いてもらいます。
User
aws s3 ls s3://himituno-bucket2/SECRET/に、1、2、・・・999というフォルダがあります。このフォルダ内にFLAGというデータがあるかどうか確認する方法を教えてください。(例:s3://himituno-bucket2/SECRET/1/FLAG)シェルはzshです。
ChatGPT
aws s3 ls コマンドを使って、各サブディレクトリ(1, 2, ..., 999)内に FLAG という名前のファイルが存在するかどうかを確認するには、以下のような zsh スクリプトを使用できます。>
# バケット名と探したいファイル名を変数に格納
bucket="himituno-bucket2"
search_folder="SECRET/"
search_file="FLAG"
# リージョンを指定
region="ap-northeast-1"
# 1から999までループ
for i in {1..999}; do
# サブディレクトリ内のオブジェクトをリストする
objects=$(aws s3 ls s3://$bucket/$search_folder$i/ --region $region)
# リスト内でFLAGが見つかるか確認する
if echo "$objects" | grep -q "$search_file"; then
echo "FLAG object found in s3://$bucket/$search_folder$i/"
# オブジェクトをダウンロード(オプション)
aws s3 cp s3://$bucket/$search_folder$i/$search_file ./ --region $region
exit 0
fi
done
このスクリプトは、1から999までの各サブディレクトリ内で FLAG という名前のオブジェクトが存在するかどうかを確認します。FLAG が見つかった場合、その場所を出力し、オプションでローカルにダウンロードします。
注意: このスクリプトは、各イテレーションで AWS S3 API を呼び出します。そのため、API レート制限に達する可能性があります。また、大量のフォルダがある場合、スクリプトの実行に時間がかかる可能性があります。
大量にAPIを呼び出して申し訳ないですが、CTFなので大丈夫なはず・・・スクリプトを実行すると数分かかりながら完了しましたが、FLAGは見つからず。
これ以上何をすればいいのかわからなくなり、ギブアップしました。
正解としては、まずaws s3 ls
コマンドに--recursive
オプションをつけて、バケット内のすべてのオブジェクトをリスト表示で確認。
すると、flag.jpgという画像ファイルが各孫フォルダに入っていることがわかるそう。FLAG、画像に書いてあったんですね〜。
大量のflag.jpgの中でFLAGの書いてる当たりはたった1枚。これを見わける方法は、ファイルサイズを比較し、ひとつだけ違うサイズの画像を見つけるそうです。そして当たりのflag.jpgの場所がわかれば、それをローカルにコピーして画像を見ればFLAGを取得することができます。
これは難しかったですが、最もハッカー気分になれて楽しかった問題です。と同時に、リスト操作の権限のあるなしによって求めるデータの探しやすさが全く違ってくることも分かりました。
Show IAM Policy
指示:このユーザーにアタッチされているポリシーを確認してみよう!Policyドキュメントを注意深くみたらFLAGが隠れているかも。
与えられたデータ:IAMユーザーのアクセスキーIDとシークレットアクセスキー
IAMポリシーとは?
誰がどの条件でどのAWSリソースにアクセスできるか、という設定のこと。大きく分けて「アタッチドポリシー(管理ポリシー)」と「インラインポリシー」の2種類がある。
アタッチドポリシー(管理ポリシー):ポリシーが単体として存在でき、複数のIAMユーザー、グループ、ロールにアタッチできる。
インラインポリシー:特定のIAMユーザー、グループ、ロールに直接埋め込まれる。
まず、aws configure
で、AWS CLIにIAMユーザーを登録します。
次にユーザーにアタッチされているポリシーを調べます。ユーザーのポリシーを調べるためにはユーザー名が必要なので、先にユーザー名を調べます。
模範解答はaws sts get-celler-identity
で調べることですが・・・。
$ aws sts get-celler-identity
{
"UserId": [略],
"Account": [略],
"Arn": "arn:aws:iam::[略]:user/ctf_challenge_5" #user/のあとの文字列がユーザー名
}
問題を解いているときはArn
にユーザー名が含まれていることを知らなかったので、IAMユーザーの一覧を取得するaws iam list-users
を打って、権限がないことによって出力されたエラー文からユーザー名を知るという変則的な方法でユーザー名を取得しました・・・。
ユーザー名がわかったので、ユーザーにアタッチされている管理ポリシーをaws iam list-attached-user-policies
コマンドで調べます。
$ aws iam list-attached-user-policies --user-name ctf_challenge_5
{
"AttachedPolicies": []
}
あれっポリシーがない!?
ここでポリシーには管理ポリシーとインラインポリシーの2種類があることに気づき、インラインポリシーをaws iam list-user-policies
コマンドで調べます。
$ aws iam list-user-policies --user-name ctf_challenge_5
{
"PolicyNames": []
}
これもない! なんで〜?
ここでChatGPTに聞いて、このユーザーが属しているIAMグループにポリシーがアタッチされている可能性に気づきました。そこで、このユーザーが所属しているIAMグループ名をaws iam list-groups-for-user
コマンドで調べます。
$ aws iam list-groups-for-user --user-name ctf_challenge_5
{
"Groups": [
{
"Path": "/",
"GroupName": "ctf5",
"GroupId": "AGPAQZ2IU22WHDOHFQVUC",
"Arn": "arn:aws:iam::055450064556:group/ctf5",
"CreateDate": "2023-08-06T14:48:22+00:00"
}
]
}
ctf5
というグループに所属していることがわかりました。
グループ名がわかれば、グループにアタッチされているポリシーを調べることができます。まずは管理ポリシーから。
aws iam list-attached-group-policies --group-name ctf5
{
"AttachedPolicies": []
}
またない・・・。インラインポリシーを確認します。
aws iam list-group-policies --group-name ctf5
{
"PolicyNames": [
"selfcheck"
]
}
ようやく実のある答えが返ってきました!
aws iam get-group-policy
でポリシーの内容を取得します。
$ aws iam get-group-policy --group-name ctf5 --policy-name selfcheck
{
"GroupName": "ctf5",
"PolicyName": "selfcheck",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": {
"Sid": "U0pBV1N7RG9feW91LWZpbmRfdGhlX0B0dGFjaGVkX3AwbDFjeT99",
"Effect": "Allow",
"Action": [
"iam:Get*",
"iam:List*"
],
"Resource": [
"arn:aws:iam::055450064556:group/ctf5",
"arn:aws:iam::055450064556:user/ctf_challenge_5"
]
}
}
}
さて、ここからどうすればよいのでしょうか。「Policyドキュメントを注意深くみる」とは・・・?
えぇい、GPTに丸投げだぁっ!
User
SJAWS{xxxxx}という形式のFLAGを探しているのですが、このポリシードキュメントにヒントはありそうでしょうか?
ChatGPT
このポリシードキュメントには、特定の形式(SJAWS{xxxxx})のFLAGに関する明確なヒントは含まれていないようです。ただし、ステートメントID(Sid)には次のような長い文字列があります:
U0pBV1N7RG9feW91LWZpbmRfdGhlX0B0dGFjaGVkX3AwbDFjeT99
この文字列はBase64エンコードされている可能性があり、デコードすると何らかのヒントやFLAGが現れるかもしれません。Base64デコードを試してみる価値があります。
Base64!!!そういうことか〜〜〜!
SidはAWS側が機械的に割り振るものなのかなと思っていたので、ここがFLAGになっているとは気付きませんでした。
Sidとは?
ポリシードキュメントに与える任意の識別子。ポリシーの説明文として使うことができる。
この解読方法に辿り着いた瞬間が、今回のCTFで脳汁最大噴射の瞬間だったかもしれません。GPT、ありがとう。
オンラインのBase64デコードツールを使って、無事FLAGを入手できました。
Run Function
指示:アクセスキーを調べてFLAGを入手せよ!
与えられたデータ:IAMユーザーのアクセスキーIDとシークレットアクセスキー
アクセスキーを調べろということなので、とりあえずaws configure
で、AWS CLIにIAMユーザーを登録します。
さて、このあとなにをすればいいのか・・・。GPTに聞いてあたりをつけます。
User
CTFを解いています。AWSのアクセスキーIDとシークレットアクセスキーが与えられています。アクセスキーを調べてFLAGを入手する方法を教えてください。問題のタイトルは「Run Function」です。
ChatGPT
CTF(Capture The Flag)のコンテキストでAWSのアクセスキーIDとシークレットアクセスキーが与えられている場合、問題のタイトル「Run Function」から推測すると、AWS Lambda関数を実行してFLAGを取得する必要があるかもしれません。
なるほど! 問題文からしても、IAMユーザーに紐づけられているLambdaの関数を実行するとFLAGが入手できそうです。
AWS Lambdaとは?
サーバーレスコンピューティングサービス。利用者はインフラ管理不要でコードの実行環境を手に入れられる。
Lambda関数とは?
Lambdaでは、「関数」という単位でプログラムコードを管理し、処理も関数単位で実行する。
Lambda関数の一覧を取得するコマンドaws lambda list-functions
を実行するとパーミッションエラーが出ました。
$ aws lambda list-functions
An error occurred (AccessDeniedException) when calling the ListFunctions operation: User: arn:aws:iam::055450064556:user/ctf_challenge_6 is not authorized to perform: lambda:ListFunctions on resource: * because no identity-based policy allows the lambda:ListFunctions action
IAMユーザーのポリシーを確認して、紐づけられているLambda関数がないか確認します。
$ aws iam list-user-policies --user-name ctf_challenge_6
{
"PolicyNames": [
"runlambda"
]
}
lambda関数に関連ありそうなインラインポリシーが設定されていました!
ポリシーの詳細を確認します。
$ aws iam get-user-policy --user-name ctf_challenge_6 --policy-name runlambda
{
"UserName": "ctf_challenge_6",
"PolicyName": "runlambda",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "readiam",
"Effect": "Allow",
"Action": [
"iam:Get*",
"iam:List*"
],
"Resource": "arn:aws:iam::055450064556:user/ctf_challenge_6"
},
{
"Sid": "lambdaInvoke",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": "arn:aws:lambda:ap-northeast-1:055450064556:function:run_me"
}
]
}
}
ポリシーを読み解いて、このIAMユーザーには run_me
という名前のLambda関数を実行する権限があることがわかりました。
IAMユーザーとLambdaのリージョンの違いに注意して、Lambda関数を実行します。
$ aws lambda invoke --function-name run_me --region ap-northeast-1 output.txt
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
実行結果を保存したoutput.txt
には、{"statusCode": 200, "body": "\"Look at the log!\""}
とありました。
Lambdaのログを見ればFLAGがありそう・・・というところまで辿り着けたのですが、AWS CLI経由でLambdaの実行ログを取得する方法がわからず、あえなくギブアップとなりました。
正解は、上記のlambda関数の実行コマンドに--log-type Tail
というオプションをつければログも表示できたみたいです。問題としてはさらにここからBase64をデコードするステップも入っていました。
Easy
Recon the website
指示:Webサイトを調査してFLAGを入手せよ!
与えられたデータ:WebアプリケーションのURL
ざっくりした指示ゆえにあたりをつけづらく、解くのを半ば諦めていたのですが、CTFの後半で運営の方がヒントを出してくれました。
「Recon the website」の問題も思ったよりも正答率が悪いのでヒントをだします!
この問題のヒントはこのサイトのHTMLソースにも書かれていますが、このWebサイトはあるAWSサービスを利用して作成されています。
こういった静的なWebサイトの公開によく利用されるサービスです。
このAWSサービスは残念なことに設定をミスっているので、それを利用すればFLAGを獲得できるかもしれません。
静的なWebサイトのホスティングによく使われるのはS3です。S3の設定ミスによって、本来見られるはずのないファイルが外部から見られるようになっていて、そこにFLAGがあるのだろうと予測しました。
GPTに聞いたところ、どうやら「バケット名」がわかっている&パブリックアクセスが許可されているという状態であれば、外部からファイルにアクセスできそうです。
通常、S3バケットの名前はURLの一部として公開されています。この名前を特定できれば、バケットに対するさまざまな操作が可能になります。
S3バケットがパブリックアクセスを許可している場合、その中のファイルも一般に公開されている可能性があります。
例: http://[バケット名].s3.amazonaws.com/[ファイル名]
さらに「AWS S3 バケット名 CTF」でググるとこちらの記事がヒット。
バケット名はドメイン名と同じになるのですね。またリージョン名のほうは事前に教えられています。
ブラウザからhttp://[バケット名=ドメイン名].s3.[リージョン名].amazonaws.com
にアクセスしたところ、期待通りS3のファイル一覧が表示され、FLAGを入手できました!
終了後の解説では、「現実世界でも度々問題になっている、S3バケットの設定不備によって情報が漏洩してしまうケースをモチーフにした問題」と説明があり、確かに内部にあるはずのリソースにこんなに簡単にアクセスできてしまうのかと理解できました。
Get Provision
指示:EC2 上で動く Web アプリケーションからインスタンスのプロビジョニングのデータを入手せよ!
与えられたデータ:WebアプリケーションのURL
プロビジョニングデータとは?
インスタンスが作成される際に設定される一連の情報やリソースのこと。インスタンスタイプ、AMI ID、ユーザーデータ(起動スクリプトや設定ファイル)などが含まれる。
これはChatGPTに聞いたらなんか解けちゃった問題です。
与えられたWebアプリケーションにアクセスすると、「脆弱なオンラインプロキシサービス」とあり、URLの入力欄があります。
User
CTFを解いています。EC2 上で動く Web アプリケーションからインスタンスのプロビジョニングのデータを入手する方法を教えてください。わかっているのは、WebアプリケーションのURLだけです。URLが下記です。
(略)
ChatGPT
(略)
このようなプロキシサービスがある場合、EC2のメタデータサービスにアクセスしてみるのは一つの手です。通常、EC2インスタンス内からしかアクセスできないこのサービスに、このようなプロキシを使って外部からアクセスできるかもしれません。
メタデータサービスのURLは通常http://169.254.169.254/latest/meta-data/
です。このURLをプロキシサービスの「アクセス先のURL」に指定してみてください。それによって、EC2インスタンスのメタデータにアクセスできるかもしれません。
教えられた通りにhttp://169.254.169.254/latest/meta-data/
を入力すると、怪しげな文字列が得られました。
User
下記の情報が得られました
Not web site
ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hostname identity-credentials/ instance-action instance-id instance-life-cycle instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/ system
ChatGPT
得られた情報によると、EC2のメタデータサービスに成功してアクセスしたようです。リストされている項目は、EC2インスタンスに関するさまざまなメタデータを表しています。
え! できてるんだ・・・。
User
EC2インスタンスのメタデータが手に入りました。インスタンスのプロビジョニングのデータを入手したいのですが、どうすればいいですか?
ChatGPT
EC2インスタンスのメタデータにアクセスできたのであれば、プロビジョニングデータ(特にユーザーデータ)も同様にメタデータサービスを通じて取得できる可能性が高いです。
EC2インスタンスが起動する際に指定されるユーザーデータは、通常、以下のURLでアクセスできます。
http://169.254.169.254/latest/user-data/
このURLをプロキシサービスの「アクセス先のURL」に指定してみてください。そうすることで、インスタンスのユーザーデータにアクセスできるかもしれません。
これも教えられた通りに、http://169.254.169.254/latest/user-data/
を入力するとFLAGの文字列が得られました。
GPTがあればサイバー攻撃だってわけもわからずできてしまうのか、と怖くなります。
作問者の方のブログを読むと、SSRF攻撃を行う問題だったみたいです。
SSRF(Server Side Request Forgery)攻撃とは?
公開サーバーを経由して内部のサーバーに攻撃する手法。
例えば、EC2には、インスタンスからhttp://169.254.169.254/
にアクセスすると、そのインスタンスの設定情報が読み込めるという機能がある。
もしEC2で公開されているWebサービスに脆弱な箇所があれば、上記の機能を悪用してEC2のクレデンシャルを読み込むことができる。
徳丸先生のSSRF(Server Side Request Forgery)徹底入門より要約。
問題のサイトは、URL欄に入力したURLをiframeのsrc属性に代入してサイト上で表示させるという仕組みになっていました。http://169.254.169.254/
にあるEC2の設定情報は通常外部からアクセスできませんが、サイト内のiframeを経由することでアクセスできるようになってしまっていたんですね。
まとめ
Security-JAWSのCTFに参加して、AWSの各種サービスの知識やAWS CLIの使い方、IAMポリシーの重要性などをハンズオンで学ぶことができました。
模範解答、他の参加者の方のwriteupを読んでさらに理解を深めたいと思います!(以下ざっとメモ)
Discussion