LINE の Webhook からのアクセスを”x-line-signature”を用いて検証する

AWS

はじめに

AWS を使用して LINE Bot を作成していましたが、API Gateway のエンドポイントを無防備にインターネットに公開していると誰でも API を叩けてしまいます。そこでセキュリティを強化するため、LINE のサーバー以外からのアクセスを拒否するように リクエストの検証を設定していきたいと思います。

手順

公式ドキュメントの確認

LINE 以外からの不正リクエスト防止に、Webhook の ”x-line-signature” リクエストヘッダに含まれる署名かを利用することが推奨されています。
今回は下記図の中心のボットサーバーが行なっている「リクエストボディから計算し、ヘッダ署名の値と一致するか確認」する処理を Lambda 関数の中で行うようにしたいと思います。

(LINE ボット開発ガイドラインより)

Lambda 関数に検証処理を追加する。

関数のコードには下記を設定します。
※今回、LINE Bot のチャネルシークレットは SSM パラメートストアから取得するように記述しています。必要に応じて環境変数等から取得するなど変更を加えてください。
※LINE ボット開発ガイドラインに下記の記載があったため、X-Line-Signature と x-line-signature いずれかの値を取得するように記述しています。

リクエストヘッダーの文字について
リクエストヘッダーのフィールド名は、大文字・小文字の表記が 予告なく変更される可能性があります。Webhookを受け取るボットサーバー側では、ヘッダーフィールド名の大文字小文字を区別せずに扱ってください。
例えば、X-Line-Signature と x-line-signature どちらの場合であっても署名の検証ができるように実装する必要があります。

LINE ボット開発ガイドラインp.18より

このオーソライザーは下記を行なっています。

  1. チャネルシークレットを秘密鍵として、HMAC-SHA256 アルゴリズムを使用してリクエストボディのダイジェスト値を取得
  2. ダイジェスト値を Base64 エンコードした値と、リクエストヘッダーのx-line-signatureに含まれる署名が一致するか検証
  3. 一致しない場合は例外が発生し、処理が終了
import base64
import hashlib
import hmac

#SSMからチャンネルシークレットを取得する
ssm = boto3.client('ssm')
channel_secret = ssm.get_parameter(Name='{SSMパラメータ名}',WithDecryption=True)['Parameter']['Value']

def lambda_handler(event, context):
    # リクエストの検証
    x_line_signature = event["headers"]["x-line-signature"] if event["headers"]["x-line-signature"] else event["headers"]["X-Line-Signature"]
    body = x_line_signature = event["body"]
    hash = hmac.new(dynamo_action.channel_secret.encode('utf-8'),
        body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    if signature != x_line_signature.encode(): raise Exception

    '''
    メインの処理
    '''

実際にリクエストをして確認してみる

下記コマンドを実行し、LINE 以外からリクエストを行ってみます。

$ curl -X POST {API Gatewayのエンドポイント} -H "x-line-signature:hoge" --data "data=hoge"

CloudWatchで Lambda のログを確認すると、例外が発生して処理が終了していることがわかります。

おわりに

リクエストの検証はLambda オーソライザーで行われることが多いですが、LINE の Webhook の検証にはリクエストのボディの値が必要になるため、今回はAPI Gatewayの後続の Lambda 関数の処理の中で検証しています。
これにより、LINE から以外のリクエストにより、Lambda 関数のメインの処理実行されることを防ぐことができました。

参考

コメント