リサーチ

窃取されたAWS Lambda認証情報悪用の検知手法

2023年4月18日(火)
著者:カウンター・スレット・ユニット(CTU™)リサーチチーム

※本記事は、https://www.secureworks.com/ で公開されている Detecting the Use of Stolen AWS Lambda Credentials を翻訳したもので、 2023年4月18日執筆時点の見解となります。

要約

Amazon Web Service(AWS)Lambdaはサーバーレスのイベント駆動型コンピューティングサービスです。FaaS(Function as a Service)型であるため、ユーザーは実行元のインフラ保守に関する複雑な作業を行わずにアプリケーション機能をデプロイすることができます。Lambdaの実行は、他のAWSサービスまたはSaaS(Software as a Service)型の外部アプリケーションのイベントをトリガーとして開始できます。

Lambda実行環境の内部には、AWS Identity and Access Management(IAM)で使用するための一時的かつ権限が限定されたAWS Security Token Service(AWS STS)の認証情報一式が格納されています。ユーザーのアプリケーションに存在する脆弱性や、設定不備のリソースを介してこれらの認証情報が攻撃者に窃取される可能性があります。認証情報を窃取した攻撃者が権限昇格し、接続を永続化したり、標的組織の1つまたは複数のAWSアカウントを横展開する恐れもあります。

Secureworks®カウンター・スレット・ユニット™(CTU)のリサーチャーはこのたび、窃取された認証情報の使用有無をAWS CloudTrailで識別する方法を考案しました。AWS Lambdaが実行される都度、AWS CloudTrailにログイベントが作成されます。このログを通常運用時のベースラインに設定しておけば、ログイベントがベースラインから逸脱した際、認証情報の窃取有無を特定できます。Lambda以外のAWSサービスにおける認証情報の窃取についても同様の手法を適用できます。別のAWSアカウントで使用されたEC2インスタンスの認証情報を検出できるAmazon GuardDutyでは、LambdaをはじめとするEC2以外のサービスは検出対象外です。さらに、同一アカウント上で使用された認証情報については検出できません。

AWS Lambdaの実行とイベントの記録

今回紹介する検知ロジックの技術を理解するためには、AWS Lambdaのアーキテクチャ、運用方法および管理APIの呼び出しに関する多少の知識が必要です。

AWS CloudTrail

AWS CloudTrailは、すべてのAWSアカウントを対象に、アカウントの活動状況とAPIの利用状況を監視して記録します。デフォルト設定では、90日分の管理イベントが記録され、AWS顧客は無償で利用できます。また、AWS Lambdaの実行およびInvokeイベントなどを含むデータイベントの記録を有効化できるオプションもありますが、こちらは有償で追加の費用がかかります。すべてのAWS顧客がデータイベントの記録を有効化しているわけではないため、今回の検知手法はAWS環境すべてで記録された管理イベントのログに依存する必要があります。

LambdaのmicroVMとWorker

AWSによると、「LambdaはAWS Lambda Workerと呼ばれるAmazon EC2インスタンスフリート上に実行環境を構築します。Workerは(中略)お客様からは見えない隔離された別のAWSアカウントのLambdaで起動・管理されます。WorkerにはFirecrackerで作成された1つまたは複数の仮想ハードウェアのマイクロ仮想マシン(microVM)を持ちます。」とのことです。図1は、共有インフラ上で2つの顧客のLambda関数が分離されている状態を示しています。

図1:AWS Lambda Workerの分離モデル(出典: AWS)

Lambda実行環境のライフサイクル

実行環境の標準的なライフサイクルには主に3つのフェーズがあります(図2)。

図2:Lambda実行環境のライフサイクル(出典: AWS)

1. 初期化(Init)フェーズ —Lambdaが設定済リソースを使用して実行環境を作成またはフリーズ解除し、関数コードとすべてのレイヤーをダウンロードして、拡張機能とランタイムを初期化し、関数の初期化コード(メインハンドラーの外部にあるコード)を実行します。このフェーズは関数の初回呼び出し時、同時実行のプロビジョニングが有効化されている場合には関数呼び出しに先立ち発生します。Initフェーズには、関数コード実行前に拡張機能とランタイムすべてのセットアップタスクを確実に完了するための3つのサブフェーズ(Extension init、Runtime init、Function init)があります。

Initフェーズ中にLambdaが実行環境を作成する初期化処理は通常「コールドスタート」と呼ばれます。シャットダウンがトリガーされる前の初期化済の環境を再度呼び出す場合は「ウォームスタート」と呼ばれます。

2. 呼び出し(Invoke)フェーズ — Lambdaが関数ハンドラーを呼び出します。関数が実行され、完了すると、Lambdaは、別の関数呼び出し処理の準備をします。

3. シャットダウン(Shutdown)フェーズ — このフェーズはLambda関数が一定期間呼び出されなかった場合にトリガーされます。AWSがどのようなロジックでこの期間を算出しているのかは不明です。シャットダウンフェーズでは、Lambdaはランタイムをシャットダウンし、拡張機能にアラートを出してそれらを完全に停止させた後、環境を削除します。

AWSによると、Workerのリース時間は最長14時間とのことです。しかし当社CTUリサーチャーが観測したライフサイクルは、これより大幅に短いものでした。Workerで使用されるAWS STS認証情報のデフォルト有効期限は12時間のため、Workerがシャットダウンした後も認証情報が引き続き有効である可能性があります。

AWSは2022年11月、Java 11のランタイム環境向けのLambda SnapStartをリリースしました。標準的なLambdaとは多少ライフサイクルが異なりますが、CTU™リサーチャーが検証したところ、検知ロジックに違いは出なかったため、標準的なライフサイクルを使った分析で問題はありません。

Lambdaの初期化とログ

Lambdaをコールドスタートする際には、毎回2種類のCloudTrailイベント(AssumeRole、CreateLogStream)が記録されます(図3)。

図3:Lambda初期化時のAPI呼び出し(出典:Secureworks)

  1. AWS顧客が定義したLambda Identity Access Management(IAM)実行ロールが、関数呼び出しサービスに対してAWS STSの認証情報を要求した時点でAssumeRoleイベントが記録されます。このイベントが実行ライフサイクルのどのタイミングで発生するのかは不明ですが、当社CTUの分析では、Lambda Workerの初期化前に発生していました。
  2. Lambda WorkerがAPIを呼び出し、CloudWatchログストリームの作成を試みます。CloudWatchのロググループが存在しない場合、LambdaによるCreateLogStream初回実行時のAPI呼び出しが失敗し、エラーコードResourceNotFoundExceptionが返されます。その際はLambda WorkerがCreateLogGroupを呼び出し、その後にCreateLogStreamを成功させます。

今回概説する検知ロジックは、以下の仮定に依存しています。

  • Lambda実行用のIAMロールがCloudWatchへのログの記録を許可されていること。通常使われるAWSマネージドポリシーAWSLambdaBasicExecutionRoleにはこれらの権限が含まれています。
  • CloudWatchのロググループは存在するが、Lambda実行ロールにログ記録の権限が付与されていない場合、CloudTrailは失敗のイベントを記録する。
  • ロググループが存在せず、実行ロールにログ記録権限が付与されていない場合、CreateLogStream呼び出しにおいてCloudTrailイベントは生成されない。

Lambda WorkerはCreateLogStreamイベントのソースになるため、同イベントには顧客のLambda関数が実行されたAWSリージョンおよびソースIPアドレスの情報が含まれます。AssumeRoleイベントのソースはWorkerではないため、これらの情報は含まれません。Invokeイベントはデータイベントに区分されるイベントで、完全性を期すために図3にもその旨区分して記載しています。ただし、このイベントが存在しないAWS環境もあるため、後述の検知ロジックでは除外しています。

図4はLambdaの初期化(Init)フェーズで作成されたAssumeRoleイベントのサンプルです。

図4:CloudTrailのAssumeRoleイベント(出典:Secureworks)

図5はCreateLogStreamイベントのサンプルを示したものです。

図5:CloudTraiのCreateLogStreamイベント(出典:Secureworks)

同時進行

時々、複数のCreateLogStreamイベントが同一のアクセスキーIDを保持していることがあります。その理由は、Lambda Workerが同一のSTS認証情報を用いて複数の処理を開始したためと考えられます。図6は、イベントのキーが一致しているもののIPアドレスが異なる2つのイベントのdiffコマンドの出力結果です。

図6:CreateLogStreamイベントの差分を比較するdiffコマンドの出力結果(出典:Secureworks)

図7は、同時実行時のLambda初期化処理のサンプルです。

図7:同時実行時のLambda初期化のAPI呼び出し(出典:Secureworks)

AWS STSキーの再利用

AWS STSのアクセスキーは時間が経過しても再利用でき、無関係のイベントに紐づけできることが当社CTUリサーチャーの調べで判明しました。図8では、2つのイベントで同一のSTSアクセスキーID(accessKeyId)を持ちますが、認証情報の引き受け元は、それぞれ異なるIAMロールを持つユーザーIDとなっています。principalIdおよびuserNameのフィールドは、IAMロールのAmazon resource name(ARN)に別々に存在するコンポーネントです。作成日時(creationDate)を比較すると、わずか11日しか経過していません。つまり、アクセスキー作成の入力値としてAWSのアカウントIDが使用された可能性があります。

図8:同一のアクセスキーIDが異なるロールに適用されたCloudTrailイベントの差分を比較するdiffコマンドの出力結果(出典:Secureworks)

Amazon Virtual Private Cloud(VPC)へのアクセス

Lambda関数は、Lambdaサービスが保有するVPC上で実行されます。LambdaはElastic Network Interface(ENI)を介して、AWSが管理するLambda VPCからAWS顧客が管理するVPCのプライベートサブネットに接続できます。LambdaにAWS顧客のVPCリソースへのアクセス権が付与され、セキュリティグループにエグレス(送信)が許可された設定の場合、CreateLogStreamイベントのソースIPアドレスは、他のイベントのパブリックソースIPアドレスと一致しません。

図9は、VPCアクセス権が付与された完全なLambda構成の例です。図中の各番号はCloudTrailイベントログ中の異なるソースIPアドレスに対応しています。CreateNetworkInterfaceおよびAllocateAddressのイベントは、AWS顧客のVPCへの接続時にLambda Workerによって作成されます。Lambda関数が他のAWSリソースと通信する際に、他のソースIPアドレスがCloudTrailに表示されることもあります。VPC Gatewayエンドポイント経由でAWS S3に接続要求する際のソースIPアドレスは、ENIに割り当てられたプライベートIPアドレス(⑤)となります。DynamoDBへの接続リクエストはインターネット経由でルーティングされ、ソースはNAT Gatewayに割り当てられたパブリックIPアドレス(⑥)となります。

図9:VPCアクセス権が付与されたLambda初期化時のAPI呼び出し(出典:Secureworks)

このVPCアクセスのシナリオでは、CreateLogStreamではなくAllocateAddressイベントを使ってパブリックIPアドレスのresponseElements.publicIpフィールドを特定しています。

図10:CloudTrailのAllocateAddressイベント(出典:Secureworks)

検知手法の概念実証

概念実証では、窃取されたLambda認証情報の悪用を示すCloudTrailのイベントをAmazon Athenaを用いて特定しました。この検知手法は移植可能であり、インシデント対応調査時などに随時、任意のAWSアカウントに適用できます。

検索ロジック

検知器を作成するためにはまず、Lambda実行ライフサイクルのInit(初期化)フェーズでWorkerが作成するCreateLogStreamイベントのメタデータを抽出する必要があります。CloudTrailのログは以下のフィールドでフィルタリングされます。

  • eventName —WorkerがCloudWatchのログ記録を設定する際に使う値’CreateLogStream’
  • userAgent —文字列'awslambda-worker’を含む値(例:awslambda-worker/1.0 rusoto/0.48.0 rust/1.67.1 linux)

上記のイベントをもとに、平常運用時のベースラインとなるテーブルが作成されます。テーブルには以下のメタデータフィールドの値が入力されます。

  • eventTime — CreateLogStreamイベントのタイムスタンプ(関連するAssumeRoleイベントの数秒後に発生する。これをもとに、STS認証情報の作成日時を推測できる)
  • userIdentity.accessKeyId — WorkerおよびLambda関数が使用するAWS STSのアクセスキーID
  • userIdentity.arn — アクセス時の認証情報に関連付けられたIAMロールのARN。STSアクセスキーIDが再使用される際、競合を回避するために使用される
  • sourceIPAddress — イベントのソースIPアドレス(Lambdaの同期実行が有効になっている場合、Lambda実行に関連する複数のIPアドスが同一の認証情報を用いる)
  • awsRegion — イベントが発生したAWSリージョンの位置情報(位置情報をもとに、イベントの詳細を特定できる)
2番目に作成されるテーブルには、AWS顧客側で割り当てたパブリックIPアドレスすべてが含まれます。これらは、NAT GatewayやEC2インスタンスなど、AWSアカウント内のリソースに関連付けられたIPアドレスです。’AllocateAddress’イベントを条件にCloudTrailのログがフィルタリングされ、以下のフィールドが抽出されます。
  • publicIp — AWSリソースに割り当てられたIPアドレス
  • allocationId — 元々のAllocateAddressイベントに対する一意の識別子
  • networkBorderGroup — AWSリージョン内でIPアドレスが割り当てられたロケーション
認証情報が窃取された、または通常のLambda実行以外で用いられたかどうかを判定するために、CloudTrailのイベント全件に対してクエリを実行します。クエリでは、ベースラインとなるLambdaイベントテーブルに存在するアクセスキーIDを持ち、以下の条件に合致するイベントを検索します。
  • sourceIPAddressが、InitフェーズでLambda Workerが使用したIPアドレスと異なっている。
  • userIdentity.arnが、Lambdaが使用したARNと一致している。
  • eventNameがDecryptではない。AWS Key Management Service(KMS)が有効化されていれば、Decryptイベントが作成されるが、今回の検知ロジックではそれらを無視している
  • eventTimeがLambdaのInitフェーズよりも後である。
  • sourceIPAddressが該当アカウント内で過去観測されていない。sourceIPAddressが、ログ保持期間以前に使用されていた古いものである場合、誤検知を招く恐れがある。ただし、CreateLogStreamのイベントとほぼ同時刻にアドレスがENIに割り当てられるため、この条件は今回の検知ロジックには支障を来たさないと考えられる。

Amazon Athenaを使った検知手法の適用

Amazon AthenaによるAWS CloudTrailログへのクエリ実行に先立ち、以下の前提条件を満たす必要があります。

  • S3バケットにログを書き込むための証跡を作成する(すでに存在する場合は不要)。AWSでは、90日以上経過したイベントの保管・保持におけるセキュリティのベストプラクティスとして証跡の作成を推奨しています。正確に検知できるよう、証跡は有効なAWSリージョンすべてのログを収集する設定にしてください。
  • CloudTrailログ用のAthenaテーブル(デフォルト名:cloudtrail_logs)を作成する。異なるテーブル名を使う場合は、AthenaクエリのFROMステートメントを該当する名前に合わせて変更してください。

上記のステップが完了すると、Amazon AthenaでCloudTrailのイベントを検索可能になり、窃取された認証情報の悪用の有無を確認できるようになります。本記事のAppendix(付録)には、平文のAthenaクエリを記載しています。リサーチャーの皆様は、検知手法の再現にご活用ください。

  1. CreateLogStreamイベントを含むテーブルを新たに作成します(図11)。クエリを実行すると、最も古いeventTimeの値を取得し、同時実行環境のソースIPアドレスを配列にまとめます。

    図11:CreateLogStreamイベントのテーブルを作成するAthenaクエリ(出典:Secureworks)

  2. AWS顧客側リソースに関連付けられたパブリックIPアドレスすべて(インターネットゲートウェイなど)を含む別のテーブルを作成します(図12)。

    図12:AWS顧客が割り当てたIPアドレスのテーブルを作成するAthenaクエリ

  3. 窃取されたLambda認証情報が用いられたイベントの検索クエリを実行します(図13)。

    図13:窃取された認証情報が用いられたイベントを抽出するAthenaクエリ(出典:Secureworks)

図14は、今回の検知手法で特定されたGetCallerIdentityイベントのサンプルです。

図14:窃取された認証情報が用いられたイベントの検知(出典:Secureworks)

値が以下のとおりであり、ただちに不審なイベントと見なすべきです。

  • awsRegionが“us-east-1”であり“us-west-2”と一致しない
  • “GetCallerIdentity”というeventNameが正規のLambdaで実行される可能性は低い
  • sourceIPAddress が“203 . 0 . 113 . 9”であり“34 . 220 . 84 . 211”と一致していない
  • sourceIPAddress “203 . 0 . 113 . 9”はAWSのIPアドレス空間内に存在しない

検知器のさらなる進化

AWSは、JSON形式のClassless Inter-Domain Routing(CIDR)表記でパブリックIPアドレスの範囲を公開しています。このデータを使うと、IPアドレスがAWS環境の外に存在するのか否かを最初に確認し、その後に計算コストの高いロジックで他の条件を検証できるため、検知ロジックの効率を改善できます。

同様の検知ロジックは、AWS Elastic Container Service(ECS)やElastic Kubernetes Service(EKS)をはじめとするLambda以外のAWSサービスにも適用できます。ECS、EKSではいずれも、AWS Fargateのサーバーレスコンピューティングエンジンを利用できます。このエンジンは、Lambda Workerと同じFirecrackerのmicroVM上に構築されています。

注意事項

Lambdaの料金はリクエスト100万件単位で算出されます。実行頻度の高い環境ではコールドスタートはあまり発生しません。ただし、リアルタイム検知を行うと、検知データベースに大量のデータが挿入され、テーブルサイズが肥大化する可能性があります。

今回の分析でご紹介した検知ロジックでは、文書化されていない機能を利用しています。Lambdaの実行方法は、AWSによって随時変更される可能性があります。

まとめ

AWS CloudTrailは管理イベントのログにおける大変優れたソースです。AWS Lambdaの運用環境で発生した特定のイベントを、他のイベントのベースラインやコンテキスト情報の判断に役立てることで、セキュリティ対策につながります。Athenaを用いた概念実証用の検知器では、悪意ある振る舞いに関するイベントを効果的に特定することができました。

Appendix(付録) — Athenaを用いたクエリ

今回、検知機能の概念実証で用いたAthenaクエリのテキスト版はこちらです。当該機能にご関心のあるセキュリティリサーチャーの皆様はご参照ください。

CreateLogStreamイベントテーブルの作成

CREATE TABLE "lambda_coldstart" AS
SELECT
useridentity.accessKeyId as accesskeyid,
-- Source IP can be different for the same access key id due to Lambda concurrency
array_agg(sourceipaddress) as sourceipaddresses,
awsregion,
useridentity.arn as arn,
array_agg(useragent) as useragents,
MIN(eventtime) as eventtime
FROM cloudtrail_logs WHERE
eventname = 'CreateLogStream' AND
useragent LIKE 'awslambda-worker%'
GROUP BY 1, 3, 4

窃取された認証情報が用いられたイベントの検索

SELECT
lcs.accesskeyid,
lcs.sourceipaddresses,
lcs.awsregion,
ct.useridentity.accessKeyId,
ct.sourceipaddress,
ct.awsregion,
ct.eventname,
ct.eventid
FROM cloudtrail_logs ct, lambda_coldstart lcs WHERE
lcs.accesskeyid = ct.useridentity.accessKeyId AND
not contains(lcs.sourceipaddresses, ct.sourceipaddress) AND
-- Exclude AWS managed services
ct.sourceipaddress != 'AWS Internal' AND
-- access keys can be reused to make sure it's the same ARN (which will differ)
ct.useridentity.arn = lcs.arn AND
-- Decrypt is noisy for the purposes of this detector
ct.eventname != 'Decrypt' AND
ct.eventtime > lcs.eventtime AND
-- Lookup IP addresses that have been allocated to account and exclude if they match
NOT EXISTS (SELECT 1 FROM allocated_addresses aa WHERE aa.publicip = ct.sourceipaddress);

今すぐ Taegis をお試しください

ご確認ください:Taegis がリスクを軽減し、既存のセキュリティ投資を最適化し、人材不足を解消することがどのようにできるかをデモでご覧ください。