はじめに
こんにちは、研究開発部の宇田川です。
今月末に開かれるセミナーで脆弱性体験アプリケーションのServerlessGoatを題材にチームメンバーが登壇するのですが、その準備で行ったLocalStack上にServerlessGoatを構築した内容を紹介します。
背景
クラウドの普及に伴い、Webアプリケーションをサーバーレスで実装する企業が増えています。
弊社の事業でもサーバーレスで構築されたWebアプリケーションを対象とする機会が増えており、時代に合ったセキュリティ対策ができるようにサーバーレスへの調査を進めています。
サーバーレスだとセキュリティってどうなの?と質問を受けることがありますが、今までのWebアプリケーションと同様に、サーバーレスであっても脆弱性が存在し影響を受けます。
その例として、セキュリティ分野の研究やガイドラインの作成をしているオープンソース・ソフトウェアコミュニティOWASPでは、サーバレスのセキュリティリスクのトップ10リストを公開しています。
見てもなかなか実感が湧かなかったのですが、このリストに含まれるリスクを体験するために意図的に脆弱性として組み込まれたのが、ServerlessGoat です。
目的は脆弱性の体験アプリケーションではありますが、MS-Wordの.docをテキストへ変換する機能を備えています。その機能の色々なところに脆弱性があるわけです。
ちょっと残念なのが、ServerlessGoatを開発したPureSecは2019年にPalo Alto Networksに買収されていて、それ以来メンテナンスが止まっていることです。学習教材として需要はあるので、またメンテナンスが再開して欲しいです。
ServerlessGoatの構成と処理の流れ
ServerlessGoatの構成を見ます。
静的コンテンツのトップページをLambdaで返しているところに違和感がありますが、それ以外は典型的なサーバレスの構成となっています。
MS-Wordの.docをテキストへ変換する流れを説明します。
- api gateway URLにアクセス
forntend lambdaが呼び出され、トップページ(index.html)が返される。 - トップページの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サービスをローカルマシンで実行することができます。
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に実行するコマンドを出しているのでそれをインストールします。
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のドキュメントを参考にしました。) docs.aws.amazon.com
samlocalインストール
そのままsamコマンドを実行するとAWS上にデプロイしようとしてしまうので、LocalStackにデプロイするようにLocalStackのプロジェクトが公開しているsamlocalをインストールします。
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}
私のデプロイした環境では
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(金)に行われるウェビナーで同じチームの辻本が解説します。
奮ってご参加ください!