新しいAWS Security Hubで危険な公開Lambda関数を検出し可視化してみた

新しいAWS Security Hubで危険な公開Lambda関数を検出し可視化してみた

新しいAWS Security HubのExposureについて理解を深めるため、ドキュメントをより咀嚼しながら実際にAWS Lambdaの露出を検出してみました。
2025.07.27

こんにちは、臼田です。

みなさん、新しいAWS Security Hub使ってますか?(挨拶

今回はプレビューで使えるAWS Security HubのExposure(露出)で、危険なLambda関数を検出して可視化してみました。

概要

先日のAWS re:Inforce 2025にて、新しいAWS Security Hubが登場し、統合セキュリティソリューションとして各種AWSセキュリティサービスから情報を収集しまとめて確認することができるようになりました。

その中でも特に、集めた情報からより危険な状態を発見し可視化する「露出(Exposure)」の機能がとても良く、私はお気に入りです。詳細は下記をご確認ください。

https://dev.classmethod.jp/articles/aws-securityhub-advanced-exposure-attackpath/

以前はEC2周りの検出を行ったのですが、今回はもう少し露出について深堀りしつつ、タイトル通り今度はLambda関数について検出して可視化してみたいと思います。

新しい検出、Exposure Findingsの仕組み

AWS Security HubではこれまでCSPMの機能やAmazon GuardDutyなどから収集してきた情報をFindings(検出)として記録していました。新しいAWS Security Hubでは露出が新しい機能として誕生しており、ここで扱うFindingsはExposure Findingsとなります。

新しいAWS Security HubではFindingsは従来のASFFからOCSFに変わっており、Exposure FindingsのフォーマットもOCSFとなっています。

以降、このExposure Findingsで扱う情報から、露出でどのようなことができるのか把握していきましょう。

Exposure Findings対応リソース

現在AWS Security Hubでは下記8種類のリソースタイプに対する露出の検出をサポートしています。

  • AWS::DynamoDB::Table
  • AWS::EC2::Instance
  • AWS::ECS::Service
  • AWS::EKS::Cluster
  • AWS::IAM::User
  • AWS::Lambda::Function
  • AWS::RDS::DBInstance
  • AWS::S3::Bucket

主要な攻撃に晒されやすいリソースがありますが、まだまだカバレッジは増やしてほしいところですね。

検出メカニズム

Exposure Findingsは従来の個別のセキュリティチェックとは異なり、複数の「特性(Trait)」を統合してリソースごとに最大1つのFindingを生成します。特性タイプは現在下記4つの種類があります。

  1. 設定ミス(Misconfiguration): 全リソース対象、CSPM検出
  2. 到達可能性(Reachability): EC2・Lambda対象、CSPM+Inspector検出
  3. 機密データ(Sensitive Data): S3対象、Macie検出
  4. 脆弱性(Vulnerability): EC2/ECS/EKS/Lambda対象、Inspector検出

1つのリソースに関する様々な特性を集計してまとめて確認できるExposure Findingsとなります。従来はバラバラに見ていく必要があったものを1つにまとめてくれるのは嬉しいですね。

Severity算出の詳細ロジック

単純にまとめるだけでなく、まとめた後にその脅威のレベルも下記5つの評価軸による総合判定を行います。

  1. 認識(Awareness): 公開エクスプロイトの存在
  2. 検出の容易さ(Ease of discovery): 自動ツールでの発見容易性
  3. 悪用の容易さ(Ease of exploit): エクスプロイトの実行容易性
  4. エクスプロイトの可能性(Likelihood of exploit): 30日以内攻撃可能性(EPSS連携)
  5. 影響(Impact): 攻撃成功時の影響範囲

このあたりはだいぶAmazon Inspectorによる評価が強く出るところですね。必ず連携して使っていきたいところです。

なおExposure Findingsは、AWS Security Hubが6時間ごとに検出を行う仕組みのため、すぐに検出されないことには注意が必要です。

どのようなExposureが検出されそうか?

対応リソースと特性タイプから下記のようなものは検出できそうかな?と考えられます。

  • EC2インスタンスの検出しそうな例
    • IMDSv1の使用
    • 管理者権限IAMロール
    • パブリックIPあり
    • 0.0.0.0/0のセキュリティグループ
    • 既知の脆弱性を持つパッケージ
  • S3バケットの検出しそうな例
    • パブリックアクセス許可
    • バージョニング無効
    • KMS暗号化未使用
    • Macieが検出したPII/金融情報
  • Lambda関数の検出しそうな例
    • 廃止予定のランタイム使用
    • パブリック実行許可
    • 依存関係の脆弱性

他にも色々考えられますが、対象リソースタイプが主体であり、だいたい外部から利用できる状態にし、追加で1つ以上の強い権限や脆弱性や機密情報などと紐づいている、というのがうまくいきそうな気がします。もちろんこれは推測なので実際にはやってみないとわかりませんね。

Lambda関数でやってみた

というわけで実際にやってみましょう。今回は、Lambda関数でExposure Findingsを意図的に発生させる実験をしてみました。どのアーキテクチャ要素が検出のトリガーになるかを確認したかったんです。

実装したアーキテクチャ

CloudFormationテンプレートで以下のような構成を作成しました。

001_lambda_exposure

テンプレートはこちらです。

CloudFormationテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Lambda Exposure Findings Test - Security Hub検証用シンプルテンプレート'

Resources:
  # Lambda実行用IAMロール(意図的に過剰権限)
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: 'exposure-test-lambda-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      # 意図的な過剰権限(Misconfiguration Trait用)
      Policies:
        - PolicyName: OverPrivilegedPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                  - dynamodb:*
                  - rds:Describe*
                Resource: '*'
      Tags:
        - Key: Purpose
          Value: 'exposure-findings-test'
        - Key: Environment
          Value: 'test'

  # Exposure Findings対象のLambda関数
  ExposureLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: 'exposure-test-function'
      Runtime: python3.8  # 意図的に古いランタイム(Misconfiguration)
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Timeout: 30
      MemorySize: 128
      # 同時実行数制限(コスト制御)
      ReservedConcurrentExecutions: 3
      # VPC設定なし(デフォルトでVPC外配置 = Misconfiguration)
      Code:
        ZipFile: |
          import json
          import time
          import os

          def lambda_handler(event, context):
              """
              Exposure Findings テスト用Lambda関数
              """

              # レート制限(コスト制御)
              time.sleep(0.5)

              # 簡易認証チェック
              auth_token = event.get('queryStringParameters', {}).get('token') if event.get('queryStringParameters') else None

              if not auth_token or auth_token != "test-token-2025":
                  return {
                      'statusCode': 401,
                      'headers': {'Content-Type': 'application/json'},
                      'body': json.dumps({'error': 'Unauthorized access'})
                  }

              # 正常レスポンス
              response_data = {
                  'message': 'Hello from exposed Lambda function!',
                  'timestamp': int(time.time()),
                  'function_name': context.function_name,
                  'request_id': context.aws_request_id,
                  'status': 'success'
              }

              return {
                  'statusCode': 200,
                  'headers': {'Content-Type': 'application/json'},
                  'body': json.dumps(response_data, indent=2)
              }
      Environment:
        Variables:
          ENVIRONMENT: 'test'
          PURPOSE: 'exposure-findings-test'
      Tags:
        - Key: Purpose
          Value: 'exposure-findings-test'
        - Key: Environment
          Value: 'test'
        - Key: Security-Test
          Value: 'true'

  # Function URL実行許可(Reachability Traitの主要因)
  LambdaUrlInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref ExposureLambdaFunction
      Action: lambda:InvokeFunctionUrl
      Principal: '*'
      FunctionUrlAuthType: NONE

  # Function URL(認証なしパブリックアクセス)
  LambdaFunctionUrl:
    Type: AWS::Lambda::Url
    Properties:
      TargetFunctionArn: !GetAtt ExposureLambdaFunction.Arn
      AuthType: NONE  # 危険:認証なし
      Cors:
        AllowCredentials: false
        AllowHeaders:
          - 'Content-Type'
        AllowMethods:
          - GET
          - POST
        AllowOrigins:
          - '*'

  # CloudWatch Logs Group(短期保存)
  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: '/aws/lambda/exposure-test-function'
      RetentionInDays: 3

Outputs:
  FunctionUrl:
    Description: 'Public Lambda Function URL'
    Value: !GetAtt LambdaFunctionUrl.FunctionUrl

  TestUrl:
    Description: 'Test URL with authentication token'
    Value: !Sub '${LambdaFunctionUrl.FunctionUrl}?token=test-token-2025'

  SecurityNotice:
    Description: 'SECURITY WARNING'
    Value: 'This Lambda function is intentionally insecure for testing purposes. DELETE after testing!'

これでいい感じに検出してくれました。

想定する検出ポイント

このテンプレートで以下の特性タイプと内容により検出される想定です。

  • 設定ミス
    • 古いランタイム: python3.8という廃止予定のランタイムを使用
    • 過剰な権限: S3やDynamoDBへの全権限を付与
  • 到達可能性
    • Function URL: 認証なし(AuthType: NONE)でインターネットから直接アクセス可能
    • パブリック実行許可: Principal: '*'でどこからでも実行可能
    • CORS設定: AllowOrigins: '*'で全てのオリジンからアクセス可能

実際の検出結果

しばらくしたら期待通りExposure Findingsが生成されました!Highのイベントとして検出されています。

002_lambda_exposure

詳細を開いてみてみましょう。

003_lambda_exposure

検出タイトルは「潜在的な資格情報の盗難: サービスレベルの管理実行ロールを持つ、パブリックに呼び出し可能なLambda | Potential Credential Stealing: Publicly invocable lambda with service-level administrative execution role」です。アタックパスも可視化されていますね!

004_lambda_exposure

実際のFinding jsonを一部抜粋したのがこちらです。

{
  "finding_info": {
    "title": "Potential Credential Stealing: Publicly invocable lambda with service-level administrative execution role",
    "desc": "The Lambda function in question is publicly invocable, meaning it can be triggered by any user or service without proper authentication. Additionally, the function has been granted an AWS role with elevated access permissions, specifically a service-level administrative execution role. This combination creates a significant security vulnerability...",
    "types": [
      "Exposure/Potential Credential Access/Unsecured Credentials"
    ],
    "related_events": [
      {
        "traits": [
          {
            "category": "Misconfiguration",
            "name": "The IAM Role associated with the Lambda function has a policy with administrative access to an AWS Service",
            "type": "Contributing Trait",
            "uid": "OverPrivilegedPolicy",
            "values": [
              "s3:*",
              "dynamodb:*"
            ]
          }
        ]
      },
      {
        "traits": [
          {
            "category": "Reachability",
            "name": "The Lambda function can be invoked by anyone",
            "type": "Contributing Trait"
          }
        ]
      },
      {
        "traits": [
          {
            "category": "Misconfiguration",
            "name": "The Lambda function is running an unsupported runtime",
            "type": "Contextual Trait"
          }
        ]
      }
    ]
  },
  "severity": "High",
  "resources": [
    {
      "type": "AWS::Lambda::Function",
      "uid": "exposure-test-function",
      "resource_relationship": {
        "name": "exposure-test-function Relationships",
        "nodes": [
          {
            "name": "Node0",
            "type": "AWS::Lambda::Function"
          },
          {
            "name": "Node1", 
            "type": "AWS::IAM::Role"
          },
          {
            "name": "Node2",
            "type": "AWS::IAM::Policy"
          }
        ],
        "edges": [
          {
            "name": "AWS::Lambda::Function->AWS::IAM::Role",
            "relation": "Is associated with",
            "source": "Node0",
            "target": "Node1"
          }
        ]
      }
    }
  ]
}

だいたい想定通りの検出になっています。アタックパス部分では、インラインポリシーで強い権限を入れている場合、リソースとして別れているわけではないのでIAM Role側に赤いバッチ(関連する特性の数)がついているのは注意ポイントですね。

特性はこのようになっています。

005_lambda_exposure

設定ミス2つと到達可能性1つの検出です。

以前検証したときのEC2での到達可能性の検出はAmazon Inspectorのものでしたが、今回のAWS Lambdaの到達可能性の検出はLambda.1 Lambda function policies should prohibit public accessでCSPM由来のものでした。後から解釈すると納得ですが、上述した到達可能性の特性タイプの仕様通りAmazon Inspector以外にCSPMによる到達可能性の検出が確認できた形です。

まとめ

露出で検出されるExposure Findingsの仕組みをもう少し深く理解しながら、新たなLambdaの公開による検出を試してみました。

露出については単体の問題ではなく、複数の要素が組み合わさり現実的な脅威になると検出されるのは非常に良いところです。その分緊急度の高い検出になりますから内容を理解し、即時対応できるようにしていきましょう。

他にもいろんなパターンで検出できると思うので、是非試して行きましょう!

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.