SSTによる安全なWebサイト運営のためのセキュリティ情報

エンジニアブログ
  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

LocalStackにServerlessGoatを構築した話

はじめに

こんにちは、研究開発部の宇田川です。

今月末に開かれるセミナーで脆弱性体験アプリケーションのServerlessGoatを題材にチームメンバーが登壇するのですが、その準備で行ったLocalStack上にServerlessGoatを構築した内容を紹介します。

背景

クラウドの普及に伴い、Webアプリケーションをサーバーレスで実装する企業が増えています。
弊社の事業でもサーバーレスで構築されたWebアプリケーションを対象とする機会が増えており、時代に合ったセキュリティ対策ができるようにサーバーレスへの調査を進めています。

サーバーレスだとセキュリティってどうなの?と質問を受けることがありますが、今までのWebアプリケーションと同様に、サーバーレスであっても脆弱性が存在し影響を受けます。

その例として、セキュリティ分野の研究やガイドラインの作成をしているオープンソース・ソフトウェアコミュニティOWASPでは、サーバレスのセキュリティリスクのトップ10リストを公開しています。

https://owasp.org/www-project-serverless-top-10/

見てもなかなか実感が湧かなかったのですが、このリストに含まれるリスクを体験するために意図的に脆弱性として組み込まれたのが、ServerlessGoat です。

https://github.com/OWASP/Serverless-Goat

目的は脆弱性の体験アプリケーションではありますが、MS-Wordの.docをテキストへ変換する機能を備えています。その機能の色々なところに脆弱性があるわけです。
ちょっと残念なのが、ServerlessGoatを開発したPureSecは2019年にPalo Alto Networksに買収されていて、それ以来メンテナンスが止まっていることです。学習教材として需要はあるので、またメンテナンスが再開して欲しいです。

ServerlessGoatの構成と処理の流れ

ServerlessGoatの構成を見ます。

 

ServerlessGoatの構成

ServerlessGoatの構成

 

静的コンテンツのトップページをLambdaで返しているところに違和感がありますが、それ以外は典型的なサーバレスの構成となっています。

MS-Wordの.docをテキストへ変換する流れを説明します。

  1. api gateway URLにアクセス
    forntend lambdaが呼び出され、トップページ(index.html)が返される。
  2. トップページのtextareaにdocファイルのURLを入力。「Submitボタンをクリック」
    apigatewayを経由し、convert lambdaが呼び出される。
    ①requestID、ソースIP、docファイルのURLの情報が、DynamoDBのテーブルに保存される。
    ②curlコマンドによりdocファイルを取得し、catdocコマンドによりdocファイルからテキストを抽出する。
    ③テキストをS3にhtmlドキュメントとして保存する。
    ④-1 S3保存に成功すれば、S3バケットのパブリックURL+テキストのファイル名へリダイレクトする。ブラウザ上はテキスト化されたdocファイルが表示される。
    ④-2 500レスポンスコードを返し、エラースタックの内容をページに表示する。

②のLambda内でのコマンドの実行、
④-1のS3バケットのパブリックURLへリダイレクト、
④-2のエラースタックの内容をページに表示
のあたりに嫌な予感がします…

ServerlessGoat問題点

上記の②、④-1、④-2の嫌な予感然り、ServerlessGoatは脆弱性学習用WEBアプリケーションだけあって、多数の脆弱性が存在します。
にもかかわらず、ServerlessGoatはそのままAWSに構築するとインターネットに公開されてしまいます。
これは攻撃してくださいと言っているようなものなので、構築には躊躇しました。
ただ、ServerlessGoatの検証する環境は欲しかったので、考えて思いついたのがLocalStack上でのServerlessGoatの構築でした。

LocalStack

LocalStackは、AWSサービスエミュレーターです。 LocalStackを使用すると、AWSに接続しなくても、API Gateway、Lambda、DynamoDB等のAWSサービスをローカルマシンで実行することができます。

https://github.com/localstack/localstack

ServerlessGoatで使用するAWSサービスは

  • API Gateway
  • Lambda
  • DynamoDB
  • S3
  • IAM

です。
これらはLocalStackの無料枠で収まるので、LocalStackの使用を決定しました。

後日発覚したこととして、ServerlessGoatはAWS SAMとフレームワークで作られているのですが、このフレームワークには「sam local start-api」というコマンドを使えばLocalStackを使用しなくてもローカル環境で操作できます。
ただ、テキスト変換の機能、試したい脆弱性がうまく動かなかったので、結果としてLocalStack上にServerlessGoatを構築する方法を選択して良かったと思っています。

構築手順

それでは実際にどのように構築したのか説明していきます。

ローカルPCのWSL、VirtualBox上のLinuxに構築することも考えましたが、チームで使う予定だったのでEC2のAmazonLinux2上に構築しました。
VPC、セキュリティグループ等は、社内からのアクセスのみ許可するように設定しました。細かい設定は本編から外れるので割愛させていただきます。

docker 、docker-composeインストール

LocalStackはdocker-composeで動かします。そのためにdockerとdocker-composeをインストールします。

dockerのインストールはこちら

$ sudo amazon-linux-extras install -y docker
$ sudo systemctl enable docker
$ sudo systemctl start  docker
$ sudo usermod -a -G docker ec2-user

docker-composeのインストールはこちら

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

LocalStackインストール

続いて、LocalStackをインストールします。

LocalStackのソースコードをダウンロード

$ git clone https://github.com/localstack/localstack

localstackのディレクトリに移動

$ cd localstack

皆で使うようにdocker-compose.ymlでこのEC2以外からでもアクセスできるように変更。
個人PCで使う場合はこの設定は要りません。

$ vi docker-compose.yml
  • L12,13
 - "127.0.0.1:4566:4566"
     ↓
 - "0.0.0.0:4566:4566"
 - "127.0.0.1:4571:4571"
     ↓
 - "0.0.0.0:4571:4571"

localstackを起動

$ docker-compose up -d

http://localhost:4566にcurlでアクセスしてみて、statusがrunningという出力が出ればOK

$ curl http://localhost:4566
{"status": "running"}

awslocal インストール

LocalStackにアクセスする際は、AWS CLIで --endpoint-url=http://localhost:4566 を付けてあげればいいのですが、
毎回付けるのは面倒です。
LocalStackのプロジェクトがawslocalというAWS CLIを実行する感覚でLocalStackに実行するコマンドを出しているのでそれをインストールします。

https://github.com/localstack/awscli-local

awslocalインストール

$ pip3 install awscli-local

デプロイ用S3バケット作成

LocalStackのS3にデプロイ用のS3バケットを作成してみます。
先ほどインストールしたawslocalコマンドを使用します。

デプロイ用S3バケットを作成。

$ awslocal s3 mb s3://deployment-bucket

作成できているか確認

$ awslocal s3 ls
2021-10-06 hh:mm:ss deployment-bucket

SAMインストール

ServerlessGoatは、AWS SAMで作られているため、AWS SAM CLIをインストールします。

AWS SAM CLIのダウンロード

$ wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip

ダウンロードした圧縮ファイルの解凍

unzip aws-sam-cli-linux-x86_64.zip -d sam-installation

AWS SAM CLI のインストール

sudo ./sam-installation/install

AWS SAM CLI のインストール確認。バージョンが出力されればOK

$ sam --version
SAM CLI, version 1.32.0

(インストールの方法は、下記AWSのドキュメントを参考にしました。)
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html#serverless-sam-cli-install-linux-sam-cli

samlocalインストール

そのままsamコマンドを実行するとAWS上にデプロイしようとしてしまうので、LocalStackにデプロイするようにLocalStackのプロジェクトが公開しているsamlocalをインストールします。

https://github.com/localstack/aws-sam-cli-local

samlocalインストール

$ pip3 install aws-sam-cli-local

ServerlessGoatデプロイ

ソースコードをダウンロート

$ git clone https://github.com/OWASP/Serverless-Goat.git

Serverless-Goatのディレクトリに移動

$ cd Serverless-Goat

そのままだと動かないので、ソースコードを少々手直し。

$ vi src/api/convert/index.js

L4に下記を追記。

AWS.config.update({apiVersion: '2015-03-31',endpoint: 'http://127.0.0.1:4566',region: 'ap-northeast-1', s3ForcePathStyle:'true'});

endpoint: 'http://127.0.0.1:4566'を付けることにより、LocalStack上のDynamoDB、S3にアクセスします。
付けないとLocalStack上のlambdaからAWS上のDynamoDB、S3にアクセスしようとしてエラーとなります。
s3ForcePathStyle:'true'は、bucket.hostnameの代わりにhostname/bucketのURLを使用するようにSDKに指示します。
LocalStack等のモックのS3にアクセスする際は、この設定が必要なようです。

event.requestContextにrequestIdが無いので、とりあえず別のresourceIdを設定。

L11

  let requestid = event.requestContext.requestId;
 ↓
  let resourceid = event.requestContext.resourceId;

L18

  'id': requestid,
     ↓
  'id': resourceid,

色々調べましたが、locatstackでevent.requestContext.requestIdが無いのが特定できず…
本番環境じゃないし、ここがServerlessGoatの脆弱性に大きく関わるわけでは無いので、別のIdでお茶を濁しました。

template.ymlを変更

$ vi temple.yml
  • L6 nodejsのランタイムを変更。そのままだと動きませんでした。これもメンテナンスがストップしている影響か…
    Runtime: nodejs8.10
    ↓
    Runtime: nodejs14.x
  • L34 これはServerlessGoatがDocファイルをTextに変換したファイルのURLをLocalStack上のS3のURLに変更しています。
    <instance-ip>は適宜LocalStackを構築したサーバのIPアドレスに変更してください。
   #BUCKET_URL: !GetAtt Bucket.WebsiteURL
  ↓
  BUCKET_URL: !Sub 'http://<instance-ip>:4566/${Bucket}'

ServerlessGoat ビルド&デプロイ

ビルド

$ samlocal build

Build Succeededという文字列が出力されれば、ビルド成功です。

デプロイ

$ samlocal deploy --stack-name serverelessgoat --region ap-northeast-1 --s3-bucket deployment-bucket

Successfully created/updated stack - serverelessgoat in ap-northeast-1という出力が出れば、デプロイ成功です。

outputsの

https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/

は、後のアクセスで必要になるので、メモ等に控えておいてください。

実際にアクセスしてみる!

それでは、実際にアクセスしてみます。

https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/にアクセスしても、LocalStack上のServerlessGoatにはアクセスできません。
アクセスの仕方はちょっと独特で、下記の形式に従いアクセスします。

http://localhost:4566/restapis/{api-id}/{stage-name}/_user_request_/{path_part}

参考 : GitHub – localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline!

私のデプロイした環境では
https://w0pvmnotnh.execute-api.ap-northeast-1.amazonaws.com/Prod/
と出力されました。
照らし合わせると

  • api-idは、w0pvmnotnh
  • stage-nameは、Prod
  • path_partは、 /

なので

http://localhost:4566/restapis/w0pvmnotnh/Prod/_user_request_/

となります。

localhostの部分を適宜、インスタンスのパブリック/プライベートIP等に変更して、ブラウザで確認します。

 

イメージ

 

これで心置きなく脆弱性検証ができるServerlessGoat環境ができました!

最後に

今回は、ServerlesGoatを安全な環境で検証するためにLocalStack上に構築してみました。

では、このサーバーレスWebアプリケーションのServerlessGoatにどんな脆弱性が潜むのか?
それにどんな対策が効果的なのか?
これは2021/10/29(金)に行われるウェビナーで同じチームの辻本が解説します。

https://www.securesky-tech.com/seminar/211029.html

奮ってご参加ください!

  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

この記事の筆者

筆者