Cloud One Application Security(Preview)でECS-Fargate上のWebアプリケーションのSQLインジェクションを検知してみた

サーバーレスやDockerコンテナのWebアプリケーション保護するCloud One Application Security(プレビュー)をECS-Fargate上で試してみました。注目している製品となります。
2020.08.23

こんにちはコカコーラ大好きカジです。

今日はサーバーレスやDockerコンテナのWebアプリケーション保護するCloud One Application Security(Preview)をECS-Fargate上で試してみました。 ECS-Fargateでのセキュリティ対策についてお問い合わせ頂くことがあるため、注目している製品となります。

Cloud One Application Securityとは?

トレンドマイクロのアプリケーション組み込み型のセキュリティ対策で、今まで直接保護ができなかったサーバーレス環境のAWS Lambdaやマネージドのコンテナ環境のECS-Fargateに使えるセキュリティ対策となります。

上記引用元の図はトレンドマイクロサイトの図

サーバーレス環境のやマネージドのコンテナ環境でのアプリケーションにおけるセキュリティ脅威は、OWASP Serverless Top 10や、OWASP API Security Projectに記載されています。

Cloud One Application Securityは、アプリケーション組込型のセキュリティ対策のため、対象言語やバージョンの確認が必要です。

テストする前にTrend Micro Cloud One™ Application Security Documentationをご確認ください。

動作イメージの図は以下となります。

上記の図は動画トレンドマイクロの「Cloud One Application Securityのご紹介」のYoutube動画の内の図となります。

前提条件

  • 現在(2020/08/22時点)はPreview版であり、今後変更される予定があるため、ご注意ください。
  • Cloud One トライアル版アカウント登録し、30日無償にてトライアルで簡単にテストしています。
  • 動画トレンドマイクロの「Cloud One Application Securityのご紹介」のYoutube動画をECS-Fargate上で試してみました。

検証環境の構成図

私の検証環境は以下の構成でECS-Fargate上で動作させて試してみました。

テスト用Webアプリケーションについて

OWASP Juice Shop Projectを利用しています。 詳細は以下のブログで紹介しています。

OWASP Juice ShopをDockerコンテナで起動しています。詳細は以下のサイトとなります。

Cloud One トライアル版アカウント登録

30日無償にてトライアルが可能です。

https://cloudone.trendmicro.com/でアカウントを作成し、登録します。

Cloud Oneにログインできると以下の画面になります。そこからApplication Securityをクリックします。

Application Security導入前作業

ここで言うSecurity Groupは、Application Security上のSecurity Group作成となります。注:AWSのSecurity Groupとは異なります。 「Create New Group」をクリック

「Group Name」に一意の名前(今回は、OWASP-JUICE-SHOP-PROTECT)を入れてCreate Groupをクリックします。

ダッシュボードの左側ペインにグループ名が作成されるため、そこの歯車マークを押して、GROUP CREDENTIALSをメモします。

  • キー:保護するアプリケーショングループに関連付けられた一意のキー
  • シークレット:保護するアプリケーショングループに関連付けられた一意のシークレット。
  • 詳細説明はこちら

ブロックするか検知のみにするかは以下の画面で設定します。SQLインジェクションの詳細設定をクリックして設定を追加します。

準備作業はこれだけなので、すごく簡単です。

Dockerコンテナの準備

コンテナファイルの環境設定に上記でメモしたキーとシークレットを記入します。

Dokcerfile

FROM node:13-alpine

WORKDIR /usr/src/app/
RUN mkdir juice-shop
WORKDIR /usr/src/app/juice-shop

RUN wget https://github.com/bkimminich/juice-shop/releases/download/v10.3.2/juice-shop-10.3.2_node13_linux_x64.tgz
RUN tar -xzvf juice-shop-10.3.2_node13_linux_x64.tgz -C /usr/src/app/juice-shop

RUN pwd
RUN ls -al

WORKDIR /usr/src/app/juice-shop/juice-shop_10.3.2

RUN npm install

RUN apk add --update python
RUN apk add make gcc g++
RUN npm i trend_app_protect

RUN sed -i -e "1i require('trend_app_protect');" app.js

ENV TREND_AP_KEY xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxx(メモしたキー値を記入)
ENV TREND_AP_SECRET xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(メモしたシークレット値を記入)

ENTRYPOINT ["npm", "start"]

ECRとECS-Fargateの構築

具体的なECRとFargateの構築手順は以下のページを参考にして下さい。

実際に利用したCloudFormationのテンプレートは本ページの下部に記載しています。

SQL Injectionの検知テスト

正常に起動し、ALBのDNS名へブラウザで接続すると、以下の「OWASP Juice shop」の画面が表示されます。

右上のAccountのところをクリックし、Loginをクリックしてユーザー名は、以下パスワードは任意で入力すると管理者権限でログインでき、SQLインジェクションが発生します。 (OWASP Juice shopはハードニングのトレーニング用のツールのため、本来は、SQLインジェクション部分も問題の一部となりネタバレとなります。)

ユーザ名に入力する値の詳細はこちらを参考にしています。結論は、入力値はユーザ名に以下を入力、パスワードは任意でログインとなります。

' or 1=1;--

Cloud Oneの管理画面での確認

1件目はローカルPC上でDockerコンテナを起動して試したものとなり、2件目の検知がECS-Fargate上で検知したものとなります。 どこでも試せるのが良いですね。

時間あたりの発生数表示の画面

最後に

まだ簡単な検知テストのみのため、他にも保護機能もありますので今後、試してしていきたいと思います。

今回の手順ですとnode.jsには導入しやすいのではないかと推測しておりますが、いかがでしょうか?

(私は開発者ではないため自信がないです。最初テストした際に、キーとシークレットの入力間違いで動作しない状態になり、すこしだけ試行錯誤したぐらいでした。)

気になった点としては、Deep Securityのように、エージェントの正常/異常のステータスが管理画面側で見れるわけではないため、動作しているかは検知テストを試してみないとわかりませんでした。

そのため導入する際にはアプリケーションデプロイ後に、アプリケーションの動作テストだけじゃなく、Application Securityの検知テストも一緒に実施して動作確認し運用することが必要と感じました。

構築に使用したCloudFormationのテンプレート

sample-ecr-owasp-juice-shop.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECR

Parameters:
  ProjectName:
    Default: owasp-juice-shop
    Type: String

Resources:
  ECR:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub ${ProjectName}-ecr

sample-fargate-owasp-juice-shop.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: Fargate and ALB Create

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - ProjectName
      - Label:
          default: "InternetALB Configuration"
        Parameters:
          - InternetALBName
          - TargetGroupName
      - Label:
          default: "Fargate for ECS Configuration"
        Parameters:
          - ECSClusterName
          - ECSTaskName
          - ECSTaskCPUUnit
          - ECSTaskMemory
          - ECSContainerName
          - ECSImageName
          - ECSServiceName
          - ECSTaskDesiredCount
      - Label:
          default: "Netowork Configuration"
        Parameters:
          - VpcId
          - ALBSecurityGroupId
          - ALBSubnetId1
          - ALBSubnetId2
          - ECSSecurityGroupId
          - ECSSubnetId1
          - ECSSubnetId2
      - Label:
          default: "Scaling Configuration"
        Parameters:
          - ServiceScaleEvaluationPeriods
          - ServiceCpuScaleOutThreshold
          - ServiceCpuScaleInThreshold
          - TaskMinContainerCount
          - TaskMaxContainerCount

    ParameterLabels:
      InternetALBName:
        default: "InternetALBName"
      TargetGroupName:
        default: "TargetGroupName"
      ECSClusterName:
        default: "ECSClusterName"
      ECSTaskName:
        default: "ECSTaskName"
      ECSTaskCPUUnit:
        default: "ECSTaskCPUUnit"
      ECSTaskMemory:
        default: "ECSTaskMemory"
      ECSContainerName:
        default: "ECSContainerName"
      ECSImageName:
        default: "ECSImageName"
      ECSServiceName:
        default: "ECSServiceName"
      ECSTaskDesiredCount:
        default: "ECSTaskDesiredCount"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  ProjectName:
    Default: owasp-juice-shop
    Type: String

  #VPCID
  VpcId:
    Description: "VPC ID"
    Type: AWS::EC2::VPC::Id

  #ALBSecurity Group
  ALBSecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id

  #ALBSubnet1
  ALBSubnetId1:
    Description: "ALB Subnet 1st"
    Type: AWS::EC2::Subnet::Id

  #ALBSubnet2
  ALBSubnetId2:
    Description: "ALB Subnet 2st"
    Type: AWS::EC2::Subnet::Id

  #ECSSecurity Group
  ECSSecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id

  #ECSSubnet1
  ECSSubnetId1:
    Description: "ECS Subnet 1st"
    Type: AWS::EC2::Subnet::Id

  #ECSSubnet2
  ECSSubnetId2:
    Description: "ECS Subnet 2st"
    Type: AWS::EC2::Subnet::Id

  #InternetALB
  InternetALBName:
    Type: String
    Default: "alb"

  #TargetGroupName
  TargetGroupName:
    Type: String
    Default: "tg"

  #ECSClusterName
  ECSClusterName:
    Type: String
    Default: "cluster"

  #ECSTaskName
  ECSTaskName:
    Type: String
    Default: "task"

  #ECSTaskCPUUnit
  ECSTaskCPUUnit:
    AllowedValues: [256, 512, 1024, 2048, 4096]
    Type: String
    Default: "256"

  #ECSTaskMemory
  ECSTaskMemory:
    AllowedValues: [256, 512, 1024, 2048, 4096]
    Type: String
    Default: "512"

  #ECSContainerName
  ECSContainerName:
    Type: String
    Default: "container"

  #ECSImageName
  ECSImageName:
    Type: String
    Default: "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/owasp-juice-shop-ecr"

  #ECSServiceName
  ECSServiceName:
    Type: String
    Default: "service"

  #ECSTaskDesiredCount
  ECSTaskDesiredCount:
    Type: Number
    Default: 1

  # Scaling params
  ServiceScaleEvaluationPeriods:
    Description: The number of periods over which data is compared to the specified threshold
    Type: Number
    Default: 1
    MinValue: 1

  ServiceCpuScaleOutThreshold:
    Type: Number
    Description: Average CPU value to trigger auto scaling out
    Default: 50
    MinValue: 0
    MaxValue: 100
    ConstraintDescription: Value must be between 0 and 100

  ServiceCpuScaleInThreshold:
    Type: Number
    Description: Average CPU value to trigger auto scaling in
    Default: 25
    MinValue: 0
    MaxValue: 100
    ConstraintDescription: Value must be between 0 and 100

  TaskMinContainerCount:
    Type: Number
    Description: Minimum number of containers to run for the service
    Default: 1
    MinValue: 1
    ConstraintDescription: Value must be at least one

  TaskMaxContainerCount:
    Type: Number
    Description: Maximum number of containers to run for the service when auto scaling out
    Default: 1
    MinValue: 1
    ConstraintDescription: Value must be at least one

Resources:
  # ------------------------------------------------------------#
  #  Target Group
  # ------------------------------------------------------------#
  TargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
      VpcId: !Ref VpcId
      Name: !Sub "${ProjectName}-${TargetGroupName}"
      Protocol: HTTP
      Port: 80
      TargetType: ip

  # ------------------------------------------------------------#
  #  Internet ALB
  # ------------------------------------------------------------#
  InternetALB:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      Name: !Sub "${ProjectName}-${InternetALBName}"
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-${InternetALBName}"
      Scheme: "internet-facing"
      LoadBalancerAttributes:
        - Key: "deletion_protection.enabled"
          Value: false
        - Key: "idle_timeout.timeout_seconds"
          Value: 60
        - Key: "access_logs.s3.enabled"
          Value: true
        - Key: "access_logs.s3.bucket"
          Value: !Sub "alb-log-${AWS::AccountId}"
      SecurityGroups:
        - !Ref ALBSecurityGroupId
      Subnets:
        - !Ref ALBSubnetId1
        - !Ref ALBSubnetId2

  ALBListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref InternetALB
      Port: 80
      Protocol: HTTP

  # ------------------------------------------------------------#
  # ECS Cluster
  # ------------------------------------------------------------#
  ECSCluster:
    Type: "AWS::ECS::Cluster"
    Properties:
      ClusterName: !Sub "${ProjectName}-${ECSClusterName}"

  # ------------------------------------------------------------#
  #  ECS LogGroup
  # ------------------------------------------------------------#
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"

  # ------------------------------------------------------------#
  #  ECS Task Execution Role
  # ------------------------------------------------------------#
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  # ------------------------------------------------------------#
  #  ECS TaskDefinition
  # ------------------------------------------------------------#
  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      Family: !Sub "${ProjectName}-${ECSTaskName}"
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE

      #ContainerDefinitions
      ContainerDefinitions:
        - Name: !Sub "${ProjectName}-${ECSContainerName}"
          Image: !Ref ECSImageName
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Ref ProjectName
          MemoryReservation: 128
          PortMappings:
            - HostPort: 3000
              Protocol: tcp
              ContainerPort: 3000

  # ------------------------------------------------------------#
  #  ECS Service
  # ------------------------------------------------------------#
  ECSService:
    Type: AWS::ECS::Service
    DependsOn: ALBListener
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: !Ref ECSTaskDesiredCount
      LaunchType: FARGATE
      LoadBalancers:
        - TargetGroupArn: !Ref TargetGroup
          ContainerPort: 3000
          ContainerName: !Sub "${ProjectName}-${ECSContainerName}"
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref ECSSecurityGroupId
          Subnets:
            - !Ref ECSSubnetId1
            - !Ref ECSSubnetId2
      ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
      TaskDefinition: !Ref ECSTaskDefinition
      PlatformVersion: 1.4.0

  # ------------------------------------------------------------#
  #  Auto Scaling Service
  # ------------------------------------------------------------#
  ServiceAutoScalingRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: application-autoscaling.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: !Sub "${ProjectName}-${ECSContainerName}-autoscaling"
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - application-autoscaling:*
                  - cloudwatch:DescribeAlarms
                  - cloudwatch:PutMetricAlarm
                  - ecs:DescribeServices
                  - ecs:UpdateService
                Resource: "*"

  ServiceScalingTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: !Ref TaskMinContainerCount
      MaxCapacity: !Ref TaskMaxContainerCount
      ResourceId: !Sub
        - service/${EcsClusterName}/${EcsDefaultServiceName}
        - EcsClusterName: !Ref ECSCluster
          EcsDefaultServiceName: !Sub "${ProjectName}-${ECSServiceName}"
      RoleARN: !GetAtt ServiceAutoScalingRole.Arn
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
    DependsOn:
      - ECSService
      - ServiceAutoScalingRole

  ServiceScaleOutPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutPolicy"
      PolicyType: StepScaling
      ScalingTargetId: !Ref ServiceScalingTarget
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 60
        MetricAggregationType: Average
        StepAdjustments:
          - ScalingAdjustment: 1
            MetricIntervalLowerBound: 0
    DependsOn: ServiceScalingTarget

  ServiceScaleInPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInPolicy"
      PolicyType: StepScaling
      ScalingTargetId: !Ref ServiceScalingTarget
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 60
        MetricAggregationType: Average
        StepAdjustments:
          - ScalingAdjustment: -1
            MetricIntervalUpperBound: 0
    DependsOn: ServiceScalingTarget

  ServiceScaleOutAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutAlarm"
      EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
      Statistic: Average
      TreatMissingData: notBreaching
      Threshold: !Ref ServiceCpuScaleOutThreshold
      AlarmDescription: Alarm to add capacity if CPU is high
      Period: 60
      AlarmActions:
        - !Ref ServiceScaleOutPolicy
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value: !Ref ECSCluster
        - Name: ServiceName
          Value: !Sub "${ProjectName}-${ECSServiceName}"
      ComparisonOperator: GreaterThanThreshold
      MetricName: CPUUtilization
    DependsOn:
      - ECSService
      - ServiceScaleOutPolicy

  ServiceScaleInAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInAlarm"
      EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
      Statistic: Average
      TreatMissingData: notBreaching
      Threshold: !Ref ServiceCpuScaleInThreshold
      AlarmDescription: Alarm to reduce capacity if container CPU is low
      Period: 300
      AlarmActions:
        - !Ref ServiceScaleInPolicy
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value: !Ref ECSCluster
        - Name: ServiceName
          Value: !Sub "${ProjectName}-${ECSServiceName}"
      ComparisonOperator: LessThanThreshold
      MetricName: CPUUtilization
    DependsOn:
      - ECSService
      - ServiceScaleInPolicy