├── runtime.txt ├── definitions ├── c2 │ └── .gitkeep ├── collection │ └── .gitkeep ├── exfiltration │ └── .gitkeep ├── credential_access │ ├── .gitkeep │ ├── access-secrets-api-server.yml │ ├── access-secrets-manager-secrets.yml │ ├── access-secrets-pod-filesystem.yml │ ├── app-creds-configmaps.yml │ └── app-creds-env.yml ├── initial_access │ └── .gitkeep ├── lateral_movement │ └── .gitkeep ├── defense_evasion │ ├── delete-kubernetes-events.yml │ ├── config-delete-rule.yml │ ├── cloudtrail-disable-trail.yml │ ├── cloudtrail-delete-trail.yml │ ├── cloudtrail-remove-sns-topic.yml │ ├── cloudtrail-disable-log-file-validation.yml │ ├── cloudtrail-disable-multiregion-logging.yml │ ├── cloudtrail-disable-global-event-logging.yml │ ├── cloudtrail-change-destination-bucket.yml │ ├── cloudtrail-alter-encryption-configuration.yml │ ├── pod-name-similarity.yml │ ├── update-guardduty-ip-set.yml │ └── add-new-guardduty-ip-set.yml ├── discovery │ ├── enumerate-pods.yml │ ├── enumerate-waf-rules.yml │ ├── list-guardduty-detectors.yml │ ├── enumerate-iam-users.yml │ ├── enumerate-iam-groups.yml │ ├── enumerate-secrets-manager.yml │ ├── enumerate-vpc-flow-logs.yml │ ├── get-guardduty-detector.yml │ ├── get-identity.yml │ ├── enumerate-nodes.yml │ ├── enumerate-iam-getaccountauthorizationdetails.yml │ ├── enumerate-rbac-permissions.yml │ └── enumerate-cloudtrail.yml ├── impact │ ├── delete-pod.yml │ ├── delete-serviceaccount.yml │ ├── delete-login-profile-for-iam-user.yml │ ├── delete-iam-role.yml │ ├── delete-iam-user.yml │ ├── delete-iam-group.yml │ ├── delete-iam-policy.yml │ ├── delete-secrets-manager-secret.yml │ └── delete-deployment.yml ├── persistence │ ├── create-serviceaccount.yml │ ├── add-iam-user.yml │ ├── create-iam-group.yml │ ├── add-api-key-to-iam-user.yml │ ├── change-current-iam-user-password.yml │ ├── create-secrets-manager-secret.yml │ ├── alter-assume-role-policy-document.yml │ ├── update-login-profile-for-iam-user.yml │ └── create-login-profile-for-iam-user.yml ├── execution │ ├── modify-lambda-function-code.yml │ ├── create-pod.yml │ ├── exec-into-container.yml │ └── sidecar-injection.yml └── privilege_escalation │ ├── add-iam-user-to-group.yml │ ├── add-policy-to-iam-user.yml │ ├── add-policy-to-role.yml │ ├── add-policy-to-iam-group.yml │ ├── update-inline-policy-for-user.yml │ ├── set-default-iam-policy-version.yml │ ├── create-iam-policy.yml │ ├── create-iam-policy-version.yml │ ├── attach-malicious-lambda-layer.yml │ ├── privileged-container.yml │ ├── writeable-hostpath-mount.yml │ └── add-role-to-new-ec2-instance.yml ├── generator ├── __init__.py ├── lib │ ├── __init__.py │ ├── helpers.py │ ├── leo_case_gen.py │ ├── sigmaexport.py │ ├── kubeapigen.py │ ├── awsapigen.py │ └── docgen.py ├── test │ ├── __init__.py │ ├── test_defs │ │ ├── basic.yml │ │ └── notimplemented.yml │ └── test_definition_ingestion.py ├── templates │ ├── lucene-query.jinja2 │ ├── python_api_core.jinja2 │ ├── cloudwatch-event.jinja2 │ ├── iam-policy.jinja2 │ ├── leo-cases.jinja2 │ ├── aws │ │ └── sigma.jinja2 │ ├── serverless.jinja2 │ ├── markdown.jinja2 │ ├── markdown-kubernetes.jinja2 │ ├── kube-resources.jinja2 │ ├── kubernetes │ │ └── sigma.jinja2 │ ├── aws_python_execution_function.jinja2 │ └── kube_python_execution_function.jinja2 ├── config.yml ├── pyproject.toml ├── requirements.txt └── generator.py ├── jupyter ├── lib │ ├── __init__.py │ ├── kubeclientlib.py │ └── leoclientlib.py ├── img │ └── OF815.png ├── pyproject.toml └── threat-actors │ └── demo-envs │ └── dharma.yml ├── output ├── leonidas │ ├── api │ │ ├── __init__.py │ │ └── api_base.py │ └── pyproject.toml ├── docs │ └── index.md ├── mkdocs.yml └── Dockerfile ├── docs ├── architecture.png ├── k8s-architecture.png ├── k8s-architecture-svc.png ├── using-leonidas.md ├── api-logging.md ├── writing-api-executors.md └── deploying-leonidas.md ├── aws-pipeline ├── ssh_config.tpl ├── s3.tf ├── provider.tf ├── outputs.tf ├── variables.tf ├── codecommit.tf ├── codepipeline.tf └── codebuild.tf ├── Makefile ├── leo ├── pyproject.toml ├── README.md └── leo.py ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── new-ttp.md ├── LICENSE ├── template-definition.yml ├── sigma-pipeline-kubernetes-to-elk.yml ├── .gitignore └── README.md /runtime.txt: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /definitions/c2/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jupyter/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generator/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generator/lib/helpers.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generator/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /definitions/collection/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /definitions/exfiltration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/leonidas/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /definitions/credential_access/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /definitions/initial_access/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /definitions/lateral_movement/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReversecLabs/leonidas/HEAD/docs/architecture.png -------------------------------------------------------------------------------- /jupyter/img/OF815.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReversecLabs/leonidas/HEAD/jupyter/img/OF815.png -------------------------------------------------------------------------------- /docs/k8s-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReversecLabs/leonidas/HEAD/docs/k8s-architecture.png -------------------------------------------------------------------------------- /aws-pipeline/ssh_config.tpl: -------------------------------------------------------------------------------- 1 | Host git-codecommit.*.amazonaws.com 2 | User ${key_id} 3 | IdentityFile ~/.ssh/id_rsa -------------------------------------------------------------------------------- /docs/k8s-architecture-svc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReversecLabs/leonidas/HEAD/docs/k8s-architecture-svc.png -------------------------------------------------------------------------------- /aws-pipeline/s3.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "codebuild_bucket" { 2 | bucket_prefix = "leonidas-codebuild" 3 | force_destroy = true 4 | } 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | netlifydocs: 2 | pip3 install poetry 3 | cd generator && poetry install && poetry run ./generator.py docs 4 | pip install mkdocs mkdocs-material 5 | cd output && mkdocs build 6 | -------------------------------------------------------------------------------- /generator/templates/lucene-query.jinja2: -------------------------------------------------------------------------------- 1 | {% for source in sources %}{% for attrib in source['attributes'] %}{{ attrib }}:{{ source['attributes'][attrib] }} {% if loop.index < source['attributes']|length %}AND{% endif %} {% endfor %}{% endfor %} -------------------------------------------------------------------------------- /aws-pipeline/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | terraform { 6 | backend "s3" { 7 | bucket = "wsec-leo-stg-tfstate" 8 | key = "tfstate/dev" 9 | region = "us-east-1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /generator/templates/python_api_core.jinja2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from api.api_base import api, app 4 | 5 | {% for category in categories %} 6 | from api import {{category}}{% endfor %} 7 | 8 | 9 | if __name__ == "__main__": 10 | app.run(debug=False) -------------------------------------------------------------------------------- /generator/templates/cloudwatch-event.jinja2: -------------------------------------------------------------------------------- 1 | resource "aws_cloudwatch_event_rule" "console" { 2 | name = "capture-aws-sign-in" 3 | description = "Capture each AWS Console Sign In" 4 | 5 | event_pattern = <", 7 | "Leo Tsaousis"] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | requests = "^2.31.0" 12 | pandas = "^1.5.3" 13 | jupyterlab = "^3.5.2" 14 | numpy = "1.26.4" 15 | 16 | [tool.poetry.dev-dependencies] 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /leo/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "leo" 3 | version = "0.2.0" 4 | description = "" 5 | authors = [ 6 | "Nick Jones ", 7 | "Leo Tsaousis"] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.7" 11 | pyyaml = "^5.3.1" 12 | requests = "^2.23.0" 13 | python-json-logger = "^0.1.11" 14 | boto3 = "^1.13.11" 15 | black = "^19.10b0" 16 | pylint = "^2.5.2" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = "^5.2" 20 | 21 | [build-system] 22 | requires = ["poetry>=0.12"] 23 | build-backend = "poetry.masonry.api" 24 | -------------------------------------------------------------------------------- /output/leonidas/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "leonidas" 3 | version = "0.2.0" 4 | description = "Leonidas Attacker Action Executor" 5 | authors = [ 6 | "Nick Jones ", 7 | "Leo Tsaousis"] 8 | license = "MIT" 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.8" 12 | flask = "^2.2.2" 13 | flask_restx = "^1.0.5" 14 | boto3 = "^1.26.60" 15 | 16 | [tool.poetry.dev-dependencies] 17 | black = "^19.10b0" 18 | pylint = "^2.5.2" 19 | 20 | [build-system] 21 | requires = ["poetry>=0.12"] 22 | build-backend = "poetry.masonry.api" 23 | -------------------------------------------------------------------------------- /output/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Leonidas Test Case Documentation 2 | site_url: http://detectioninthe.cloud 3 | site_description: Documentation on the cloud attack detection test cases defined as part of Leonidas 4 | copyright: Copyright 2024 WithSecure Consulting 5 | theme: 6 | name: material 7 | icon: 8 | logo: material/cloud 9 | markdown_extensions: 10 | - codehilite 11 | - toc: 12 | permalink: true 13 | extra: 14 | social: 15 | - icon: fontawesome/brands/github-alt 16 | link: https://github.com/WithSecureLabs 17 | - icon: fontawesome/brands/x-twitter 18 | link: https://x.com/withconsulting -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: NJonesUK 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Run '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: NJonesUK 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /generator/templates/leo-cases.jinja2: -------------------------------------------------------------------------------- 1 | --- 2 | apikey: 3 | url: 4 | sleeptime: 10 5 | identity: 6 | # role_arn: 7 | # access_key_id: 8 | # secret_access_key: 9 | # region: {{ config["region"] }} 10 | cases: 11 | {% for case in cases %} 12 | {{ case.name|lower|replace(" ", "_") }}: 13 | # {{ case.description|replace("\n", "") }} 14 | name: "{{ case.name }}" 15 | path: "{{ case.category|lower|replace(" ", "_") }}/{{ case.name|lower|replace(" ", "_") }}" 16 | args:{% if case.input_arguments %}{% for arg in case.input_arguments %} 17 | # {{ case.input_arguments[arg].description}} 18 | {{ arg }}: "{{ case.input_arguments[arg].value }}" {% endfor %}{% endif %} 19 | {% endfor %} -------------------------------------------------------------------------------- /generator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "generator" 3 | version = "0.2.0" 4 | description = "Generator for Leonidas and Cloud Detection Docs" 5 | authors = [ 6 | "Nick Jones", 7 | "Leo Tsaousis"] 8 | license = "MIT" 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.8" 12 | jinja2 = "^3.1.3" 13 | mkdocs = "^1.1.2" 14 | mkdocs-material = "^9.5.29" 15 | pyyaml = "^6.0.1" 16 | black = "^19.10b0" 17 | pylint = "^2.5.2" 18 | typer = "^0.2.1" 19 | docker = "^6.0.1" 20 | jinja2-ansible-filters = "^1.3.2" 21 | pysigma = "^0.11.9" 22 | pysigma-backend-elasticsearch = "^1.1.2" 23 | requests = "< 2.32.0" 24 | 25 | [tool.poetry.dev-dependencies] 26 | 27 | [build-system] 28 | requires = ["poetry>=0.12"] 29 | build-backend = "poetry.masonry.api" 30 | -------------------------------------------------------------------------------- /output/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | # Copy over generated Python code and install it 4 | COPY leonidas/pyproject.toml /leonidas/pyproject.toml 5 | COPY leonidas/leonidas.py /leonidas/leonidas.py 6 | COPY leonidas/api/ /leonidas/api/ 7 | WORKDIR /leonidas 8 | RUN pip install . 9 | 10 | # Download and validate latest kubectl binary 11 | RUN curl -sLO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ 12 | && curl -sLO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256" \ 13 | && echo "$(cat kubectl.sha256) kubectl" | sha256sum --check \ 14 | && chmod +x kubectl 15 | 16 | ENV PATH="$PATH:/leonidas" 17 | 18 | EXPOSE 5000 19 | 20 | CMD ["python", "leonidas.py"] -------------------------------------------------------------------------------- /leo/README.md: -------------------------------------------------------------------------------- 1 | ## Leo - Test Case Orchestrator 2 | 3 | Leo is a helper script designed to make it easier to execute killchains as a whole, as opposed to individual test cases. To execute a suite of test cases in Leonidas in an automated fashion. To set Leo up, run the following: 4 | 5 | * `pip install poetry` 6 | * `cd ./leo` 7 | * `poetry install` 8 | 9 | To generate the config file for Leo: 10 | 11 | * `cd generator && poetry run python generator.py leo` to generate the test case definitions for Leo 12 | * `cp ./output/caseconfig/caseconfig.yml ./leo` 13 | * edit the `caseconfig.yml` file in `./leo` to set the URL, API gateway API key, and to modify/reorder/remove test cases as required 14 | 15 | To execute the cases you've configured: 16 | 17 | * `poetry run ./leo.py caseconfig.yml` -------------------------------------------------------------------------------- /generator/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | astroid==2.4.1 3 | attrs==19.3.0 4 | black==19.10b0 5 | click==7.1.2 6 | colorama==0.4.3; sys_platform == "win32" 7 | future==0.18.3 8 | isort==4.3.21 9 | jinja2==2.11.3 10 | joblib==1.2.0; python_version > "2.7" 11 | lazy-object-proxy==1.4.3 12 | livereload==2.6.1 13 | lunr==0.5.8 14 | markdown==3.2.2 15 | markupsafe==1.1.1 16 | mccabe==0.6.1 17 | mkdocs==1.2.3 18 | mkdocs-material==5.2.2 19 | mkdocs-material-extensions==1.0 20 | nltk==3.9; python_version > "2.7" 21 | pathspec==0.8.0 22 | pygments==2.15.0 23 | pylint==2.5.2 24 | pymdown-extensions==7.1 25 | pyyaml==5.4 26 | regex==2020.5.14 27 | six==1.15.0 28 | toml==0.10.1 29 | tornado==6.4.1 30 | tqdm==4.46.1; python_version > "2.7" 31 | typed-ast==1.4.1 32 | typer==0.2.1 33 | wrapt==1.12.1 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-ttp.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New TTP 3 | about: Suggest a new attacker TTP that we should consider adding 4 | title: '' 5 | labels: '' 6 | assignees: NJonesUK 7 | 8 | --- 9 | 10 | **What is the attacker TTP you'd like to see added?** 11 | A clear and concise description of the TTP and why you think it should be added, including any examples of it being used by threat groups or in open source tooling. 12 | 13 | **How does an attacker execute it?** 14 | A clear and concise step-by-step description of any necessary CLI commands, API calls or similar needed to execute the test cases, or a link to documentation or tooling that describes this. 15 | 16 | **Where do you expect to see telemetry for this?** 17 | A clear and concise description of which data sources would contain indicators of this activity. 18 | -------------------------------------------------------------------------------- /aws-pipeline/outputs.tf: -------------------------------------------------------------------------------- 1 | output "artifact_bucket" { 2 | value = "${aws_s3_bucket.codebuild_bucket}" 3 | } 4 | 5 | output "repo" { 6 | value = "${aws_codecommit_repository.repo}" 7 | } 8 | 9 | output "codecommit_iam_user" { 10 | value = "${aws_iam_user.codecommit_user}" 11 | } 12 | 13 | output "codecommit_iam_user_key" { 14 | value = "${aws_iam_user_ssh_key.codecommit_user_key}" 15 | } 16 | 17 | data "template_file" "ssh_config" { 18 | template = "${file("${path.module}/ssh_config.tpl")}" 19 | vars = { 20 | key_id = "${aws_iam_user_ssh_key.codecommit_user_key.id}" 21 | key = "${var.codecommit_ssh_key}" 22 | } 23 | } 24 | 25 | variable "ssh_config_template" { 26 | default = "ssh_config.tpl" 27 | } 28 | 29 | 30 | output "ssh_config" { 31 | value = "${templatefile(var.ssh_config_template, {key_id = aws_iam_user_ssh_key.codecommit_user_key.id, key = var.codecommit_ssh_key})}" 32 | } 33 | -------------------------------------------------------------------------------- /definitions/defense_evasion/delete-kubernetes-events.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete Events 3 | author: Leo Tsaousis 4 | category: "Defense Evasion" 5 | description: | 6 | Delete all Kubernetes events within a namespace 7 | mitre_ids: 8 | - T1070 9 | platform: kubernetes 10 | permissions: 11 | - namespaced: true 12 | apiGroups: [""] 13 | resources: 14 | - events 15 | verbs: 16 | - delete 17 | - list 18 | input_arguments: 19 | executors: 20 | sh: 21 | code: | 22 | kubectl delete events --all 23 | leonidas_kube: 24 | implemented: True 25 | detection: 26 | sigma_id: 3132570d-cab2-4561-9ea6-1743644b2290 27 | status: experimental 28 | level: medium 29 | sources: 30 | - name: audit 31 | attributes: 32 | apiGroup: "" 33 | resource: events 34 | verb: delete 35 | references: 36 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Delete%20K8S%20events/ -------------------------------------------------------------------------------- /generator/templates/aws/sigma.jinja2: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ name }} 3 | id: {{ detection.sigma_id }} 4 | status: {{ detection.status }} 5 | author: {{ author }} 6 | date: {{ last_modified }} 7 | description: {{ description | trim }} 8 | logsource: 9 | service: {{ detection.sources[0]["name"] }} 10 | detection: 11 | selection_source: 12 | - eventSource: "{{ detection["sources"][0]["attributes"]["eventSource"] }}" 13 | events: 14 | - eventName: "{{ detection["sources"][0]["attributes"]["eventName"] }}" 15 | condition: selection_source and events 16 | level: {{ detection["level"] }} 17 | {% if mitre_ids %}tags: 18 | {% for id in mitre_ids %}- attack.{{ id }} 19 | {% endfor %}{% endif %} 20 | {% if references %}references:{% for reference in references %} 21 | - {{ reference }}{% endfor %}{% endif %} 22 | {% if detection.falsepositives %}falsepositives:{% for fp in detection.falsepositives %} 23 | - {{ fp }}{% endfor %}{% endif %} -------------------------------------------------------------------------------- /definitions/discovery/enumerate-pods.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate pods 3 | author: Leo Tsaousis 4 | category: "Discovery" 5 | description: | 6 | Enumerate pods within the Leonidas namepsace 7 | mitre_ids: 8 | - T1580 9 | - T1613 10 | platform: kubernetes 11 | permissions: 12 | - namespaced: true 13 | apiGroups: [""] # "" indicates the core API group 14 | resources: 15 | - pods 16 | verbs: 17 | - list 18 | input_arguments: 19 | executors: 20 | sh: 21 | code: | 22 | kubectl get pods 23 | leonidas_kube: 24 | implemented: True 25 | detection: 26 | sigma_id: 18490e7b-f1f3-484a-806b-4cb16aa225ce 27 | status: experimental 28 | level: low 29 | sources: 30 | - name: audit 31 | attributes: 32 | resource: pods 33 | verb: list 34 | falsepositives: 35 | - Legitimate administrative activity. Investigate for similar activity from the same identity that could indicate enumeration attempts 36 | -------------------------------------------------------------------------------- /definitions/impact/delete-pod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete pod 3 | author: Leo Tsaousis 4 | category: "Impact" 5 | description: | 6 | Remove a pod from a cluster to impact business operations 7 | mitre_ids: 8 | - T1498 9 | platform: kubernetes 10 | permissions: 11 | - namespaced: true 12 | apiGroups: [""] 13 | resources: 14 | - pods 15 | verbs: 16 | - delete 17 | input_arguments: 18 | podname: 19 | description: Name of the pod to remove 20 | type: str 21 | value: "leonidas-netutils-pod" 22 | executors: 23 | sh: 24 | code: | 25 | kubectl delete pod {{ podname }} 26 | leonidas_kube: 27 | implemented: True 28 | detection: 29 | sigma_id: 40967487-139b-4811-81d9-c9767a92aa5a 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: audit 34 | attributes: 35 | resource: pods 36 | verb: delete 37 | references: 38 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Data%20destruction/ -------------------------------------------------------------------------------- /generator/test/test_defs/basic.yml: -------------------------------------------------------------------------------- 1 | name: Enumerate Cloudtrails for Current Region 2 | description: | 3 | An adversary may attempt to enumerate the configured trails, to identify what actions will be logged and where they will be logged to. In AWS, this may start with a single call to enumerate the trails applicable to the default region. 4 | category: "Discovery" 5 | platform: aws 6 | mitre_ids: 7 | permissions: 8 | - cloudtrail:DescribeTrails 9 | input_arguments: 10 | executors: 11 | sh: 12 | code: | 13 | aws cloudtrail describe-trails 14 | leonidas_aws: 15 | implemented: True 16 | clients: 17 | - cloudtrail 18 | code: | 19 | client = boto3.client('cloudtrail') 20 | result = client.describe_trails() 21 | detection: 22 | sigma_id: GUID 23 | status: experimental 24 | level: low 25 | sources: 26 | - name: "cloudtrail" 27 | attributes: 28 | eventName: "DescribeTrails" 29 | eventSource: "*.cloudtrail.amazonaws.com" -------------------------------------------------------------------------------- /generator/test/test_defs/notimplemented.yml: -------------------------------------------------------------------------------- 1 | name: Enumerate Cloudtrails for Current Region 2 | description: | 3 | An adversary may attempt to enumerate the configured trails, to identify what actions will be logged and where they will be logged to. In AWS, this may start with a single call to enumerate the trails applicable to the default region. 4 | category: "Discovery" 5 | platform: aws 6 | mitre_ids: 7 | permissions: 8 | - cloudtrail:DescribeTrails 9 | input_arguments: 10 | executors: 11 | sh: 12 | code: | 13 | aws cloudtrail describe-trails 14 | leonidas_aws: 15 | implemented: False 16 | clients: 17 | - cloudtrail 18 | code: | 19 | client = boto3.client('cloudtrail') 20 | result = client.describe_trails() 21 | detection: 22 | sigma_id: GUID 23 | status: experimental 24 | level: low 25 | sources: 26 | - name: "cloudtrail" 27 | attributes: 28 | eventName: "DescribeTrails" 29 | eventSource: "*.cloudtrail.amazonaws.com" -------------------------------------------------------------------------------- /definitions/discovery/enumerate-waf-rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate WAF Rules 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to enumerate the rulesets applied to any configured WAFs, to aid further exploitation of applications 6 | category: Discovery 7 | mitre_ids: 8 | - T1518.001 9 | platform: aws 10 | permissions: 11 | - wafv2:ListWebACLs 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws waf list-web-acls 17 | leonidas_aws: 18 | implemented: False 19 | code: | 20 | raise NotImplementedError 21 | detection: 22 | sigma_id: c5dc6f58-05f1-48ae-8b39-1c441729517b 23 | status: experimental 24 | level: low 25 | sources: 26 | - name: cloudtrail 27 | attributes: 28 | eventName: ListWebACLs 29 | eventSource: waf.amazonaws.com 30 | falsepositives: 31 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 32 | -------------------------------------------------------------------------------- /definitions/discovery/list-guardduty-detectors.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: List GuardDuty Detectors 3 | author: Nick Jones 4 | description: | 5 | None 6 | category: "Discovery" 7 | mitre_ids: 8 | - T1518.001 9 | platform: aws 10 | permissions: 11 | - guardduty:ListDetectors 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws guardduty list-detectors 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - guardduty 21 | code: | 22 | result = clients["guardduty"].list_detectors() 23 | detection: 24 | sigma_id: 79212574-fe46-4d50-8376-74dbcffb0f22 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "ListDetectors" 31 | eventSource: "*.ec2.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/impact/delete-serviceaccount.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete service account 3 | author: Leo Tsaousis 4 | category: "Impact" 5 | description: | 6 | Delete a Kubernetes service account 7 | mitre_ids: 8 | - 1531 9 | platform: kubernetes 10 | permissions: 11 | - namespaced: true 12 | apiGroups: [""] 13 | resources: 14 | - serviceaccounts 15 | verbs: 16 | - delete 17 | input_arguments: 18 | serviceaccount: 19 | description: Name of the service account to delete 20 | type: str 21 | value: "leonidas-created-service" 22 | executors: 23 | sh: 24 | code: | 25 | kubectl delete serviceaccount {{ serviceaccount }} 26 | leonidas_kube: 27 | implemented: True 28 | detection: 29 | sigma_id: 00d40e2c-a605-4ea5-8efd-af0e8386cbea 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: audit 34 | attributes: 35 | resource: serviceaccounts 36 | verb: delete 37 | references: 38 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Data%20destruction/ 39 | -------------------------------------------------------------------------------- /definitions/persistence/create-serviceaccount.yml: -------------------------------------------------------------------------------- 1 | name: Create service account 2 | author: Leo Tsaousis 3 | category: "Persistence" 4 | description: | 5 | Create a Kubernetes service account 6 | mitre_ids: 7 | - T1136 8 | platform: kubernetes 9 | permissions: 10 | - namespaced: true 11 | apiGroups: [""] 12 | resources: 13 | - serviceaccounts 14 | verbs: 15 | - create 16 | input_arguments: 17 | serviceaccount: 18 | description: Name of the service account to create 19 | type: str 20 | value: "leonidas-created-service" 21 | executors: 22 | sh: 23 | code: | 24 | kubectl create serviceaccount {{ serviceaccount }} 25 | leonidas_kube: 26 | implemented: True 27 | detection: 28 | sigma_id: e31bae15-83ed-473e-bf31-faf4f8a17d36 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: audit 33 | attributes: 34 | resource: serviceaccounts 35 | verb: create 36 | references: 37 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/container%20service%20account/ -------------------------------------------------------------------------------- /definitions/discovery/enumerate-iam-users.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate IAM users 3 | author: Nick Jones 4 | category: "Discovery" 5 | description: | 6 | An adversary may attempt to enumerate the configured IAM users within an account, to identify entities that they might wish to gain access to or backdoor. 7 | mitre_ids: 8 | - T1033 9 | platform: aws 10 | permissions: 11 | - iam:ListUsers 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws iam list-users 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - iam 21 | code: | 22 | result = clients["iam"].list_users() 23 | detection: 24 | sigma_id: 329a2783-4410-47b2-a113-36200ab1037a 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "ListUsers" 31 | eventSource: "*.iam.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/discovery/enumerate-iam-groups.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate IAM groups 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to enumerate the configured IAM groups within an account, to identify entities that they might wish to gain access to or backdoor. 6 | platform: aws 7 | category: Discovery 8 | mitre_ids: 9 | - T1069.003 10 | permissions: 11 | - iam:ListGroups 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws iam list-groups 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - iam 21 | code: | 22 | result = clients["iam"].list_groups() 23 | detection: 24 | sigma_id: 88d0e794-1e66-4d93-bf3b-4628bd09aaa3 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "ListGroups" 31 | eventSource: "*.iam.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/discovery/enumerate-secrets-manager.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: List Secrets in Secrets Manager 3 | author: Nick Jones 4 | category: "Discovery" 5 | description: | 6 | An adversary may attempt to enumerate the secrets in secrets manager, in order to find secrets to access. 7 | mitre_ids: 8 | - T1528 9 | platform: aws 10 | permissions: 11 | - secretsmanager:ListSecrets 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws secretsmanager list-secrets 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - secretsmanager 21 | code: | 22 | result = clients["secretsmanager"].list_secrets() 23 | detection: 24 | sigma_id: 40b578f3-5056-42b8-ae6b-13e5b015d817 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "ListSecrets" 31 | eventSource: "*.secretsmanager.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 F-Secure LABS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /definitions/discovery/enumerate-vpc-flow-logs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate VPC Flow Logs 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to enumerate which VPCs have flow logs configured, to identify what actions will be logged and where they will be logged to. 6 | category: "Discovery" 7 | mitre_ids: 8 | - T1526 9 | platform: aws 10 | permissions: 11 | - ec2:DescribeFlowLogs 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws ec2 describe-flow-logs 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - ec2 21 | code: | 22 | result = clients["ec2"].describe_flow_logs() 23 | detection: 24 | sigma_id: 1e1cb77a-3ee5-476b-bf20-c233f0742a8f 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "DescribeFlowLogs" 31 | eventSource: "*.ec2.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/execution/modify-lambda-function-code.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Modify Lambda Function Code 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to modify the code that a lambda function executes in order to gain a foothold in the environment 6 | platform: aws 7 | category: Execution 8 | mitre_ids: 9 | - T1059 10 | permissions: 11 | - lambda:UpdateFunctionCode 12 | input_arguments: 13 | functionname: 14 | description: Name of the function to be targeted 15 | type: str 16 | value: "example-function" 17 | zipfile: 18 | description: Filename of the zip file of code to be uploaded 19 | type: str 20 | value: "file.zip" 21 | executors: 22 | sh: 23 | code: | 24 | aws lambda update-function-code --function-name {{ functionname }} --zip-file {{ zipfile }} --publish 25 | leonidas_aws: 26 | implemented: False 27 | code: | 28 | raise NotImplementedError 29 | detection: 30 | sigma_id: 7890b11c-19b3-4fb9-bbec-cae87db769ca 31 | status: experimental 32 | level: low 33 | sources: 34 | - name: cloudtrail 35 | attributes: 36 | eventName: None 37 | eventSource: None 38 | -------------------------------------------------------------------------------- /definitions/impact/delete-login-profile-for-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete login profile for existing user 3 | author: Nick Jones 4 | description: | 5 | None 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1531 10 | permissions: 11 | - iam:DeleteLoginProfile 12 | input_arguments: 13 | user: 14 | description: IAM user to delete the login profile for 15 | type: str 16 | value: "root" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam delete-login-profile -user-name user 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].delete_login_profile( 27 | UserName=user 28 | ) 29 | detection: 30 | sigma_id: 7c3333ce-9d4b-4704-8311-a4b68fe0f5f9 31 | status: experimental 32 | level: low 33 | sources: 34 | - name: cloudtrail 35 | attributes: 36 | eventName: DeleteLoginProfile 37 | eventSource: iam.amazonaws.com 38 | falsepositives: 39 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 40 | -------------------------------------------------------------------------------- /definitions/discovery/get-guardduty-detector.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Get GuardDuty Detector 3 | author: Nick Jones 4 | description: | 5 | None 6 | category: "Discovery" 7 | mitre_ids: 8 | - T1518.001 9 | platform: aws 10 | permissions: 11 | - guardduty:GetDetector 12 | input_arguments: 13 | detectorid: 14 | description: ID of guardduty detector 15 | type: str 16 | value: NONE 17 | executors: 18 | sh: 19 | code: | 20 | aws guardduty get-detector --detector-id {{ detectorid }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - guardduty 25 | code: | 26 | result = clients["guardduty"].get_detector(DetectorId=detectorid) 27 | detection: 28 | sigma_id: 6fc4c001-6f00-46e1-9168-5717b5f7068a 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "GetDetector" 35 | eventSource: "*.ec2.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/persistence/add-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add an IAM User 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to create an IAM user, in order to provide another means of authenticating to the AWS account 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1136.003 10 | permissions: 11 | - iam:CreateUser 12 | input_arguments: 13 | user: 14 | description: IAM user to create 15 | type: str 16 | value: "example-user" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam create-user --user-name {{ user }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].create_user(UserName=user) 27 | detection: 28 | sigma_id: 6f660a21-0fcd-4b51-9894-4d2d8213f45b 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: cloudtrail 33 | attributes: 34 | eventName: CreateUser 35 | eventSource: iam.amazonaws.com 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/impact/delete-iam-role.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete IAM Role 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to delete an IAM role within an account, to alter legitimate access or block administrative activity. 6 | platform: aws 7 | category: Impact 8 | mitre_ids: 9 | - T1531 10 | permissions: 11 | - iam:DeleteRole 12 | input_arguments: 13 | role: 14 | description: IAM role to delete 15 | type: str 16 | value: "example_role" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam delete-role --role-name {{ role }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].delete_role(RoleName=role) 27 | detection: 28 | sigma_id: 999d40d4-9f65-4b5c-ad2d-349a07a4b6c3 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeleteRole" 35 | eventSource: "*.iam.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/impact/delete-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete IAM user 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to delete an IAM user within an account, to alter legitimate access or block administrative activity. 6 | platform: aws 7 | category: Impact 8 | mitre_ids: 9 | - T1531 10 | permissions: 11 | - iam:DeleteUser 12 | input_arguments: 13 | user: 14 | description: IAM user to delete 15 | type: str 16 | value: "example_user" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam delete-user --user-name {{ user }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].delete_user(UserName=user) 27 | detection: 28 | sigma_id: aeffd059-cd63-4ff5-ac5f-63c79237c6fa 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeleteUser" 35 | eventSource: "*.iam.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/defense_evasion/config-delete-rule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete AWS Config Rule 3 | author: Nick Jones 4 | description: | 5 | None 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - config:DeleteConfigRule 12 | input_arguments: 13 | rulename: 14 | description: Name of the rule to delete 15 | type: str 16 | value: "example-rule" 17 | executors: 18 | sh: 19 | code: | 20 | aws configservice delete-config-rule --config-rule-name {{ rulename }} 21 | leonidas_aws: 22 | implemented: False 23 | client: 24 | - ConfigService 25 | code: | 26 | result = clients["ConfigService"].delete_config_rule(ConfigRuleName=rulename) 27 | detection: 28 | sigma_id: 5934f0e7-e252-4f8c-bf2c-372da6ada60a 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeleteConfigRule" 35 | eventSource: "*.config.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/impact/delete-iam-group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete IAM group 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to delete an IAM group within an account, to alter legitimate access or block administrative activity. 6 | platform: aws 7 | category: Impact 8 | mitre_ids: 9 | - T1531 10 | permissions: 11 | - iam:DeleteGroup 12 | input_arguments: 13 | group: 14 | description: IAM group to delete 15 | type: str 16 | value: "example_group" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam delete-group --group-name {{ group }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].delete_group(GroupName=group) 27 | detection: 28 | sigma_id: 84d2c61d-2882-4223-880d-5b69dce1c1d4 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeleteGroup" 35 | eventSource: "*.iam.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/persistence/create-iam-group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create IAM group 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to create an IAM group within an account, to alter legitimate access or block administrative activity. 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:CreateGroup 12 | input_arguments: 13 | group: 14 | description: IAM group to create 15 | type: str 16 | value: "example_group" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam create-group --group-name {{ group }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].create_group(GroupName=group) 27 | detection: 28 | sigma_id: 4b33f970-ef9d-49e0-ae7d-040e60d96415 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "CreateGroup" 35 | eventSource: "*.iam.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/credential_access/access-secrets-api-server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Access Secrets from API Server 3 | author: Leo Tsaousis 4 | category: "Credential Access" 5 | description: | 6 | Enumerate cluster secrets by querying the API server 7 | 8 | This test case only simulates a standard "list" verb, although the same result can also be achieved with a "watch" operation. The associated detection shall therefore not be considered complete, but only a 1-to-1 match of this particular test case. 9 | mitre_ids: 10 | - T1552.007 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: [""] 15 | resources: 16 | - secrets 17 | verbs: 18 | - list 19 | input_arguments: 20 | executors: 21 | sh: 22 | code: | 23 | kubectl get secrets -o json 24 | leonidas_kube: 25 | implemented: True 26 | detection: 27 | sigma_id: eeb3e9e1-b685-44e4-9232-6bb701f925b5 28 | status: experimental 29 | level: low 30 | sources: 31 | - name: audit 32 | attributes: 33 | apiGroup: "" 34 | resource: secrets 35 | verb: 36 | - list 37 | references: 38 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/List%20K8S%20secrets/ -------------------------------------------------------------------------------- /definitions/discovery/get-identity.yml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | name: STS Get Caller Identity 4 | author: Nick Jones 5 | description: | 6 | An adversary may attempt to verify their current identity with the credentials they hold. This could both be to verify that the credentials they hold are valid, and to get more information on their current identity for reconnaissance purposes. 7 | category: "Discovery" 8 | mitre_ids: 9 | - T1087.004 10 | platform: aws 11 | permissions: 12 | - sts:GetCallerIdentity 13 | input_arguments: 14 | executors: 15 | sh: 16 | code: | 17 | aws sts get-caller-identity 18 | leonidas_aws: 19 | implemented: true 20 | clients: 21 | - sts 22 | code: | 23 | result = clients['sts'].get_caller_identity() 24 | detection: 25 | sigma_id: b96b69c7-b1d2-44a3-9c53-f419233cac95 26 | status: experimental 27 | level: low 28 | sources: 29 | - name: "cloudtrail" 30 | attributes: 31 | eventName: "GetCallerIdentity" 32 | eventSource: "*.sts.amazonaws.com" 33 | falsepositives: 34 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 35 | -------------------------------------------------------------------------------- /definitions/discovery/enumerate-nodes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate nodes 3 | author: Leo Tsaousis 4 | category: "Discovery" 5 | description: | 6 | Enumerate nodes within a cluster 7 | 8 | This test case only simulates a standard "list" verb, although the same result can also be achieved with a "watch" operation. The associated detection shall therefore not be considered complete, but only a 1-to-1 match of this particular test case. 9 | mitre_ids: 10 | - T1580 11 | - T1613 12 | platform: kubernetes 13 | permissions: 14 | - namespaced: false 15 | apiGroups: [""] # "" indicates the core API group 16 | resources: 17 | - nodes 18 | verbs: 19 | - list 20 | input_arguments: 21 | executors: 22 | sh: 23 | code: | 24 | kubectl get nodes 25 | leonidas_kube: 26 | implemented: True 27 | detection: 28 | sigma_id: 7609f875-66d0-445e-ab16-8b3e53b1edc9 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: audit 33 | attributes: 34 | apiGroup: "" 35 | resource: nodes 36 | verb: list 37 | falsepositives: 38 | - Legitimate administrative activity. Investigate for similar activity from the same identity that could indicate enumeration attempts 39 | -------------------------------------------------------------------------------- /definitions/impact/delete-iam-policy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete IAM Policy 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to delete an IAM policy within an account, to alter legitimate access or block administrative activity. 6 | platform: aws 7 | category: Impact 8 | mitre_ids: 9 | - T1531 10 | permissions: 11 | - iam:DeletePolicy 12 | input_arguments: 13 | policy: 14 | description: ARN of the IAM policy to delete 15 | type: str 16 | value: "EXAMPLEARNHERE" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam delete-policy --policy-arn {{ policy }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].delete_policy(PolicyArn=policy) 27 | detection: 28 | sigma_id: d24b1d06-5da8-47a6-b3e2-be701113cf6e 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeletePolicy" 35 | eventSource: "*.iam.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/discovery/enumerate-iam-getaccountauthorizationdetails.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate IAM Permissions with GetAccountAuthorizationDetails 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to enumerate the configured IAM users within an account, to identify entities that they might wish to gain access to or backdoor. 6 | platform: aws 7 | category: "Discovery" 8 | mitre_ids: 9 | - T1069.003 10 | permissions: 11 | - iam:GetAccountAuthorizationDetails 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws iam get-account-authorization-details 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - iam 21 | code: | 22 | result = clients["iam"].get_account_authorization_details() 23 | detection: 24 | sigma_id: 53597a1f-06bd-4a81-9378-7e889fed52c4 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "GetAccountAuthorizationDetails" 31 | eventSource: "*.iam.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-disable-trail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail disable trail 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to disable a cloudtrail instance in order to avoid detection 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:StopLogging 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail stop-logging --name {{ trailname }} 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].stop_logging(Name=trailname) 27 | detection: 28 | sigma_id: bf856088-70f3-498b-af19-f061c0bd7740 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "StopLogging" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/persistence/add-api-key-to-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add API key to existing IAM user 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to maintain access by creating an API key attached to an existing privileged user 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:CreateAccessKey 12 | input_arguments: 13 | user: 14 | description: IAM user to generate the API key for 15 | type: str 16 | value: "root" 17 | executors: 18 | sh: 19 | code: | 20 | aws iam create-access-key --user-name {{ user }} 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - iam 25 | code: | 26 | result = clients["iam"].create_access_key( 27 | UserName=user 28 | ) 29 | detection: 30 | sigma_id: 1570ea27-492c-4615-a518-59155ba03416 31 | status: experimental 32 | level: low 33 | sources: 34 | - name: cloudtrail 35 | attributes: 36 | eventName: CreateAccessKey 37 | eventSource: iam.amazonaws.com 38 | falsepositives: 39 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/discovery/enumerate-rbac-permissions.yml: -------------------------------------------------------------------------------- 1 | name: List Own Permissions 2 | author: Leo Tsaousis 3 | category: "Discovery" 4 | description: | 5 | List the RBAC permissions assigned to the current entity 6 | 7 | In the early stages of a breach attackers will aim to list the permissions they have within the compromised environment. In a Kubernetes cluster, this can be achieved by interacting with the SelfSubjectAccessReview API, e.g. via "kubectl auth" command. This will enumerate the Role-Based Access Controls (RBAC) rules defining the compromised user's authorization. 8 | mitre_ids: 9 | - T1069.003 10 | - T1087.004 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: 15 | - authorization.k8s.io 16 | resources: 17 | - selfsubjectrulesreviews 18 | verbs: 19 | - create 20 | input_arguments: 21 | executors: 22 | sh: 23 | code: | 24 | kubectl auth can-i --list 25 | leonidas_kube: 26 | implemented: True 27 | detection: 28 | sigma_id: 84b777bd-c946-4d17-aa2e-c39f5a454325 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: audit 33 | attributes: 34 | apiGroup: authorization.k8s.io 35 | resource: selfsubjectrulesreviews 36 | verb: create -------------------------------------------------------------------------------- /definitions/discovery/enumerate-cloudtrail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enumerate Cloudtrails for a Given Region 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to enumerate the configured trails, to identify what actions will be logged and where they will be logged to. In AWS, this may start with a single call to enumerate the trails applicable to the default region. 6 | category: Discovery 7 | mitre_ids: 8 | - T1526 9 | platform: aws 10 | permissions: 11 | - cloudtrail:DescribeTrails 12 | input_arguments: 13 | executors: 14 | sh: 15 | code: | 16 | aws cloudtrail describe-trails 17 | leonidas_aws: 18 | implemented: True 19 | clients: 20 | - cloudtrail 21 | code: | 22 | result = clients["cloudtrail"].describe_trails() 23 | detection: 24 | sigma_id: 48653a63-085a-4a3b-88be-9680e9adb449 25 | status: experimental 26 | level: low 27 | sources: 28 | - name: "cloudtrail" 29 | attributes: 30 | eventName: "DescribeTrails" 31 | eventSource: "*.cloudtrail.amazonaws.com" 32 | falsepositives: 33 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 34 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-delete-trail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail delete trail 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to disable a cloudtrail instance in order to avoid detection 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:StopLogging 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail stop-logging --name {{ trailname }} 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].stop_logging(Name=trailname) 27 | detection: 28 | sigma_id: bf856088-70f3-498b-af19-f061c0bd7740 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "StopLogging" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /generator/lib/leo_case_gen.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class LeoCaseGen: 5 | """ 6 | Generates the yaml config file for Leo 7 | """ 8 | 9 | def __init__(self, config, env): 10 | self.config = config 11 | self.templates = {} 12 | self.templates["leo-cases"] = env.get_template("leo-cases.jinja2") 13 | 14 | def generate_leo_cases(self, definitions, outdir): 15 | """ 16 | Generate the yaml config file for Leo 17 | """ 18 | cases = [] 19 | for case in definitions.case_set: 20 | if case["platform"] == "aws": 21 | if case["executors"]["leonidas_aws"]["implemented"]: 22 | cutcase = {} 23 | keylist = ["name", "input_arguments", "description", "category"] 24 | cutcase = {key: value for key, value in case.items() if key in keylist} 25 | cases.append(cutcase) 26 | if not os.path.exists(outdir): 27 | os.makedirs(outdir) 28 | with open(os.path.join(outdir, "caseconfig.yml"), "w") as outfile: 29 | outfile.write( 30 | self.templates["leo-cases"] 31 | .render({"cases": cases, "config": self.config}) 32 | .replace("\n\n", "\n") 33 | ) 34 | -------------------------------------------------------------------------------- /definitions/persistence/change-current-iam-user-password.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change Password for Current User 3 | author: Nick Jones 4 | description: | 5 | None 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:ChangePassword 12 | input_arguments: 13 | oldpassword: 14 | description: Previous password 15 | type: str 16 | value: "oldpassword" 17 | newpassword: 18 | description: New password to set 19 | type: str 20 | value: "newpassword" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam change-password --old-password {{ oldpassword }} --new-password {{ newpassword }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].change_password(OldPassword=oldpassword, NewPassword=newpassword) 31 | detection: 32 | sigma_id: d3f79034-a239-40bb-815f-e1cdd91e648e 33 | status: experimental 34 | level: low 35 | sources: 36 | - name: cloudtrail 37 | attributes: 38 | eventName: ChangePassword 39 | eventSource: iam.amazonaws.com 40 | falsepositives: 41 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 42 | -------------------------------------------------------------------------------- /aws-pipeline/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "The AWS region to deploy into (default: us-west-2)." 3 | default = "us-west-2" 4 | } 5 | 6 | variable "repo_default_branch" { 7 | description = "The name of the default repository branch (default: master)" 8 | default = "master" 9 | } 10 | 11 | variable "environment" { 12 | description = "The environment being deployed (default: dev)" 13 | default = "dev" 14 | } 15 | 16 | variable "build_timeout" { 17 | description = "The time to wait for a CodeBuild to complete before timing out in minutes (default: 5)" 18 | default = "10" 19 | } 20 | 21 | variable "build_compute_type" { 22 | description = "The build instance type for CodeBuild (default: BUILD_GENERAL1_SMALL)" 23 | default = "BUILD_GENERAL1_SMALL" 24 | } 25 | 26 | variable "build_image" { 27 | description = "The build image for CodeBuild to use (default: aws/codebuild/standard:6.0)" 28 | default = "aws/codebuild/standard:6.0" 29 | } 30 | 31 | variable "package_buildspec" { 32 | description = "The buildspec to be used for the Package stage (default: buildspec.yml)" 33 | default = "buildspec.yml" 34 | } 35 | 36 | variable "codecommit_ssh_key" { 37 | description = "SSH key for codecommit user" 38 | default = "~/.ssh/id_rsa.pub" 39 | } 40 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-remove-sns-topic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail remove SNS topic 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to remove the SNS topic from a trail configuration to degrade log delivery and ingestion 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:UpdateTrail 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail update-trail --name {{ trailname }} --sns-topic-name '' 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].update_trail(Name=trailname, SnsTopicName="") 27 | detection: 28 | sigma_id: ff61896f-d9a6-40f8-8bb9-0b8c4d214af0 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "UpdateTrail" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /generator/templates/serverless.jinja2: -------------------------------------------------------------------------------- 1 | # serverless.yml 2 | 3 | service: leonidas 4 | 5 | plugins: 6 | - serverless-python-requirements 7 | - serverless-wsgi 8 | 9 | custom: 10 | wsgi: 11 | app: leonidas.app 12 | packRequirements: false 13 | pythonRequirements: 14 | slim: false 15 | usePoetry: false 16 | 17 | provider: 18 | name: aws 19 | runtime: python3.10 20 | stage: dev 21 | region: {{ region }} 22 | apiGateway: 23 | apiKeys: 24 | - apikey1 25 | iamManagedPolicies: 26 | - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 27 | iamRoleStatements: 28 | - Effect: Allow 29 | Action:{% for perm in permissions %} 30 | - {{ perm }}{% endfor %} 31 | - sts:AssumeRole 32 | Resource:{% for resource in resources %} 33 | - {{ resource }}{% endfor %} 34 | logs: 35 | restApi: true 36 | 37 | functions: 38 | app: 39 | handler: wsgi_handler.handler 40 | timeout: 10 41 | events: 42 | - http: 43 | path: / 44 | method: ANY 45 | - http: 46 | path: /swaggerui/{proxy+} 47 | method: ANY 48 | - http: 49 | path: /swagger.json 50 | method: ANY 51 | - http: 52 | path: /{proxy+} 53 | method: ANY 54 | private: true 55 | -------------------------------------------------------------------------------- /definitions/impact/delete-secrets-manager-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete Secret in Secrets Manager 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to delete secrets stored in secrets manager, in order to negatively impact the function of an environment 6 | platform: aws 7 | category: Impact 8 | mitre_ids: 9 | - T1485 10 | permissions: 11 | - secretsmanager:DeleteSecret 12 | input_arguments: 13 | secretid: 14 | description: ID of secret to access, either ARN or friendly name 15 | type: str 16 | value: "leonidas_created_secret" 17 | executors: 18 | sh: 19 | code: | 20 | aws secretsmanager list-secrets 21 | leonidas_aws: 22 | implemented: True 23 | clients: 24 | - secretsmanager 25 | code: | 26 | result = clients["secretsmanager"].delete_secret(SecretId=secretid, ForceDeleteWithoutRecovery=True) 27 | detection: 28 | sigma_id: c8f201c3-705f-4897-8cab-c765eeb4b1a3 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "DeleteSecret" 35 | eventSource: "*.secretsmanager.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-disable-log-file-validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail disable log file validation 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to disable log file validation to enable them to tamper with the logs 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:UpdateTrail 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail update-trail --name {{ trailname }} --no-enable-log-file-validation 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].update_trail(Name=trailname, EnableLogFileValidation=False) 27 | detection: 28 | sigma_id: e0608025-7e8e-4b26-8ac8-e7711d3df52f 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "UpdateTrail" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /template-definition.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Access Secret in Secrets Manager 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to access the secrets in secrets manager, to steal certificates, credentials or other sensitive material 6 | platform: aws 7 | category: Credential Access 8 | mitre_ids: 9 | - T1528 10 | permissions: 11 | - secretsmanager:GetSecretValue 12 | - kms:Decrypt 13 | input_arguments: 14 | secretid: 15 | description: ID of secret to access, either ARN or friendly name 16 | type: str 17 | value: "leonidas_created_secret" 18 | executors: 19 | sh: 20 | code: | 21 | aws secretsmanager get-secret-value --secret-id {{ secretid }} 22 | leonidas_aws: 23 | implemented: True 24 | clients: 25 | - secretsmanager 26 | code: | 27 | result = clients["secretsmanager"].get_secret_value(SecretId=secretid) 28 | detection: 29 | sigma_id: cbeba6f0-019e-4782-8c7e-e21b10521eed 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: "cloudtrail" 34 | attributes: 35 | eventName: "GetSecretValue" 36 | eventSource: "*.secretsmanager.amazonaws.com" 37 | falsepositives: 38 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 39 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/add-iam-user-to-group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add an IAM User to a Group 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to add an IAM user to a group, in order to escalate their privileges 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:AddUserToGroup 12 | input_arguments: 13 | group: 14 | description: Group to add user to 15 | type: str 16 | value: "example-group" 17 | user: 18 | description: IAM user to add to group 19 | type: str 20 | value: "example-user" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam add-user-to-group --group-name {{ group }} --user-name {{ user }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].add_user_to_group(GroupName=group, UserName=user) 31 | detection: 32 | sigma_id: 6e467337-484c-4b11-8a83-fb92af74afed 33 | status: experimental 34 | level: low 35 | sources: 36 | - name: cloudtrail 37 | attributes: 38 | eventName: AddUserToGroup 39 | eventSource: iam.amazonaws.com 40 | falsepositives: 41 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 42 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-disable-multiregion-logging.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail disable multi-region logging 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to disable multi-region logging in order to perform actions in other regions without detection 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:UpdateTrail 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail update-trail --name {{ trailname }} --no-is-multi-region-trail 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].update_trail(Name=trailname, IsMultiRegionTrail=False) 27 | detection: 28 | sigma_id: 2bc6d6d1-fde2-4767-b1e3-809aa8f5c200 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "UpdateTrail" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-disable-global-event-logging.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail disable global event logging 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to disable global event logging in order to modify configuration of global services such as IAM 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:UpdateTrail 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | executors: 18 | sh: 19 | code: | 20 | aws cloudtrail update-trail --name {{ trailname }} --no-include-global-service-events 21 | leonidas_aws: 22 | implemented: true 23 | clients: 24 | - cloudtrail 25 | code: | 26 | result = clients["cloudtrail"].update_trail(Name=trailname, IncludeGlobalServiceEvents=False) 27 | detection: 28 | sigma_id: e7b423d5-abd1-4685-988a-cf718c4d2f98 29 | status: experimental 30 | level: low 31 | sources: 32 | - name: "cloudtrail" 33 | attributes: 34 | eventName: "UpdateTrail" 35 | eventSource: "*.cloudtrail.amazonaws.com" 36 | falsepositives: 37 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 38 | -------------------------------------------------------------------------------- /definitions/credential_access/access-secrets-manager-secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Access Secret in Secrets Manager 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to access the secrets in secrets manager, to steal certificates, credentials or other sensitive material 6 | platform: aws 7 | category: Credential Access 8 | mitre_ids: 9 | - T1528 10 | permissions: 11 | - secretsmanager:GetSecretValue 12 | - kms:Decrypt 13 | input_arguments: 14 | secretid: 15 | description: ID of secret to access, either ARN or friendly name 16 | type: str 17 | value: "leonidas_created_secret" 18 | executors: 19 | sh: 20 | code: | 21 | aws secretsmanager get-secret-value --secret-id {{ secretid }} 22 | leonidas_aws: 23 | implemented: True 24 | clients: 25 | - secretsmanager 26 | code: | 27 | result = clients["secretsmanager"].get_secret_value(SecretId=secretid) 28 | detection: 29 | sigma_id: cbeba6f0-019e-4782-8c7e-e21b10521eed 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: "cloudtrail" 34 | attributes: 35 | eventName: "GetSecretValue" 36 | eventSource: "*.secretsmanager.amazonaws.com" 37 | falsepositives: 38 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 39 | -------------------------------------------------------------------------------- /definitions/persistence/create-secrets-manager-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create Secret in Secrets Manager 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to create secrets in secrets manager 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1527 10 | permissions: 11 | - secretsmanager:CreateSecret 12 | input_arguments: 13 | name: 14 | description: Name of secret to create 15 | type: str 16 | value: "leonidas_created_secret" 17 | secretstring: 18 | description: Value of secret to create 19 | type: str 20 | value: "totallysecretvalue" 21 | executors: 22 | sh: 23 | code: | 24 | aws secretsmanager create-secret --name {{ name }} --secret-string "{{ secretstring }}" 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - secretsmanager 29 | code: | 30 | result = clients["secretsmanager"].create_secret(Name=name, SecretString=secretstring) 31 | detection: 32 | sigma_id: 289f5a24-9113-4bd3-a9f3-71af8f583b88 33 | status: experimental 34 | level: low 35 | sources: 36 | - name: "cloudtrail" 37 | attributes: 38 | eventName: "CreateSecret" 39 | eventSource: "*.secretsmanager.amazonaws.com" 40 | falsepositives: 41 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 42 | -------------------------------------------------------------------------------- /jupyter/lib/kubeclientlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | from datetime import datetime 5 | 6 | class Client(object): 7 | def __init__(self, url, sa_token=None): 8 | self.url = url 9 | self.sa_token = sa_token 10 | self.execution_log = {} 11 | # { 12 | # timestamp: ["discovery/enumerate_pods", success], 13 | # "duration": [50, 40, 45] 14 | # } 15 | 16 | def run_case(self, path, args=None, verb="get"): 17 | if args is None: 18 | args = {} 19 | case_url = self.url + path 20 | # Decide request verb in a semi-assisted way, as the client can't tell from just the arguments passed 21 | if ( args=={} or args is None ) and verb != "put": 22 | args.update({"token":self.sa_token}) 23 | r = requests.get(case_url, headers={"accept":"application/json"}, params=args) 24 | else: 25 | args.update({"token":self.sa_token}) 26 | if verb=="put": 27 | r = requests.put(case_url, headers={"accept":"application/json"}, params=args) 28 | else: 29 | r = requests.post(case_url, headers={"accept":"application/json"}, params=args) 30 | 31 | success = not bool(r.json()["exit_code"]) 32 | self.execution_log.update( 33 | { datetime.now() : [path,success] } 34 | ) 35 | return r.json() 36 | 37 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-change-destination-bucket.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cloudtrail change destination bucket 3 | author: Nick Jones 4 | description: | 5 | Alter cloudtrail log destination to a bucket that target does not have access to 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - cloudtrail:UpdateTrail 12 | input_arguments: 13 | trailname: 14 | description: Name of the cloudtrail to be targeted 15 | type: str 16 | value: "example-cloudtrail" 17 | bucketname: 18 | description: Name of S3 bucket to redirect logs to 19 | type: str 20 | value: "example-bucket" 21 | executors: 22 | sh: 23 | code: | 24 | aws cloudtrail update-trail --name {{ trailname }} --s3-bucket-name {{ bucketname }} 25 | leonidas_aws: 26 | implemented: true 27 | clients: 28 | - cloudtrail 29 | code: | 30 | result = clients["cloudtrail"].update_trail(Name=trailname, S3BucketName=bucketname) 31 | detection: 32 | sigma_id: 9be7e8b4-dd76-4396-b3ca-10c6d8df1048 33 | status: experimental 34 | level: low 35 | sources: 36 | - name: "cloudtrail" 37 | attributes: 38 | eventName: "UpdateTrail" 39 | eventSource: "*.cloudtrail.amazonaws.com" 40 | falsepositives: 41 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 42 | -------------------------------------------------------------------------------- /generator/templates/markdown.jinja2: -------------------------------------------------------------------------------- 1 | # {{ case['name'] }} 2 | 3 | | Platform | Author | Last Update | 4 | | ---------------------- | -------------------- | --------------------------- | 5 | | {{ case["platform"] }} | {{ case["author"] }} | {{ case["last_modified"] }} | 6 | 7 | {{ case['description'] | trim}} 8 | 9 | ## MITRE IDs 10 | {% for id in case["mitre_ids"] %} 11 | * [{{ id }}](https://attack.mitre.org/techniques/{{ id }}/){% endfor %} 12 | 13 | ## Required Permissions 14 | 15 | {% for permission in case['permissions'] %}* {{ permission }} 16 | {% endfor %} 17 | ## Required Parameters 18 | {% if case['input_arguments'] %} 19 | | Name | Type | Description | Example Value | 20 | | ---------- | --------------------- | ---------------------------- | ---------------------- | 21 | {% for name, details in case['input_arguments'].items() %}| {{ name }} | {{ details['type'] }} | {{ details['description'] }} | {{ details['value'] }} | 22 | {% endfor %}{% else %} 23 | None{% endif %} 24 | ## Attacker Action 25 | 26 | ```bash 27 | {{ case['compiled_command'] }} 28 | ``` 29 | 30 | ## Detection Case 31 | 32 | ### ELK query 33 | 34 | When logs are ingested into ELK, the following Lucene query can be used to identify relevant events. 35 | 36 | ``` 37 | {{ case['lucene_query'] }} 38 | ``` 39 | 40 | ### Sigma Definition 41 | 42 | ```yaml 43 | {{ case["sigma"] }} 44 | ``` -------------------------------------------------------------------------------- /definitions/privilege_escalation/add-policy-to-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add a policy to a user 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to add a policy to a user, in order to escalate the privileges of that user. 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:AttachUserPolicy 12 | input_arguments: 13 | user: 14 | description: User to add policy to 15 | type: str 16 | value: root 17 | policyarn: 18 | description: Policy to add to user 19 | type: str 20 | value: arn:aws:iam::aws:policy/ReadOnlyAccess 21 | executors: 22 | sh: 23 | code: | 24 | aws iam attach-user-policy --user-name {{ user['value'] }} --policy-arn {{ policyarn['value'] }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].attach_user_policy( 31 | UserName=user, 32 | PolicyArn=policyarn 33 | ) 34 | detection: 35 | sigma_id: ca08ef1e-c37a-4a7e-b1a0-670519faacc2 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: AttachUserPolicy 42 | eventSource: "iam.amazonaws.com" 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 45 | -------------------------------------------------------------------------------- /generator/test/test_definition_ingestion.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import jinja2 3 | import os 4 | import yaml 5 | 6 | from lib.definitions import Definitions 7 | 8 | 9 | class TestDefinitionIngestion(unittest.TestCase): 10 | def setUp(self): 11 | self.env = jinja2.Environment( 12 | loader=jinja2.FileSystemLoader(os.path.join(".", "templates")), 13 | autoescape=jinja2.select_autoescape(["html", "xml"]), 14 | ) 15 | self.config = {"definitions_path": "./definitions", "output_dir": "./output"} 16 | self.basic_casefile = False 17 | self.notimplemented_casefile = None 18 | 19 | def test_single_case_ingestion(self): 20 | """ 21 | Test whether a basic definition with a single case in it loads properly 22 | """ 23 | filedata = yaml.safe_load( 24 | open(os.path.join("test", "test_defs", "basic.yml"), "r") 25 | ) 26 | definitions = Definitions(self.config, self.env) 27 | definitions._generate_definitions(filedata) 28 | self.assertEqual(len(definitions.categories), 1, "Should only be one category") 29 | self.assertEqual( 30 | "Discovery" in definitions.categories, 31 | True, 32 | "Should be in the Discovery category", 33 | ) 34 | self.assertEqual(len(definitions.case_set), 1, "Should be a single case") 35 | 36 | 37 | if __name__ == "__main__": 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /definitions/defense_evasion/cloudtrail-alter-encryption-configuration.yml: -------------------------------------------------------------------------------- 1 | name: Cloudtrail alter encryption configuration 2 | author: Nick Jones 3 | description: | 4 | Alter cloudtrail encryption configuration such that log ingestion can no longer read logs 5 | platform: aws 6 | category: Defense Evasion 7 | mitre_ids: 8 | - T1562 9 | permissions: 10 | - cloudtrail:UpdateTrail 11 | input_arguments: 12 | trailname: 13 | description: Name of the cloudtrail to be targeted 14 | type: str 15 | value: "example-cloudtrail" 16 | kmskeyid: 17 | description: KMS key ID to use, supply an empty string to disable encryption 18 | type: str 19 | value: "" 20 | executors: 21 | sh: 22 | code: | 23 | aws cloudtrail update-trail --name {{ trailname }} --kms-key-id {{ kmskeyid }} 24 | leonidas_aws: 25 | implemented: true 26 | clients: 27 | - cloudtrail 28 | code: | 29 | result = clients["cloudtrail"].update_trail(Name=trailname, KmsKeyId=kmskeyid) 30 | detection: 31 | sigma_id: 76e19d12-2ed2-4dfc-b9e9-f3b235ee471a 32 | status: experimental 33 | level: low 34 | sources: 35 | - name: "cloudtrail" 36 | attributes: 37 | eventName: "UpdateTrail" 38 | eventSource: "*.cloudtrail.amazonaws.com" 39 | falsepositives: 40 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 41 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/add-policy-to-role.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add a policy to a role 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to add a policy to a role, in order to grant additional privileges to a compromised resource. 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:AttachUserPolicy 12 | input_arguments: 13 | role: 14 | description: Role to add policy to 15 | type: str 16 | value: ReadOnlyRole 17 | policyarn: 18 | description: Policy to add to Role 19 | type: str 20 | value: arn:aws:iam::aws:policy/ReadOnlyAccess 21 | executors: 22 | sh: 23 | code: | 24 | aws iam attach-role-policy --role-name {{ role['value'] }} --policy-arn {{ policyarn['value'] }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].attach_role_policy( 31 | RoleName=role, 32 | PolicyArn=policyarn 33 | ) 34 | detection: 35 | sigma_id: cdf3b0fc-0c45-4bb4-89f2-1c6b2661ec52 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: AttachRolePolicy 42 | eventSource: "iam.amazonaws.com" 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 45 | -------------------------------------------------------------------------------- /definitions/credential_access/access-secrets-pod-filesystem.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Access Secrets from Pod 3 | author: Leo Tsaousis 4 | category: "Credential Access" 5 | description: | 6 | Access secrets within our own pod's filesystem 7 | 8 | This test case simulates an adversary within a pod e.g. in the case of a compromised workload. As this operation would not go through the API server, no Audit event will be recorded and therefore no detection signature can be authored with the log sources currently available to Sigmahq. Instead, the detection's log source is set to the non-existent "Falco" source, should equivalent functionality be onboarded in the future. 9 | mitre_ids: 10 | - T1552.001 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: [""] 15 | resources: [""] 16 | verbs: [""] 17 | input_arguments: 18 | executors: 19 | sh: 20 | code: | 21 | find /var/run/secrets/ -type f -exec cat {} \; 22 | leonidas_kube: 23 | implemented: True 24 | detection: # Action not detectable via Audit logs. Present signature using an indicative alternative log source 25 | sigma_id: 98a31be4-f1b6-47ed-9a7c-c564e4c7687b 26 | status: unsupported # to ensure this does not find its way in production systems 27 | level: low 28 | sources: 29 | - name: falco # indicative hypothetical log source 30 | attributes: 31 | filepathAccessed: /var/run/secrets # indicative filter for that hypothetical log source 32 | 33 | -------------------------------------------------------------------------------- /definitions/defense_evasion/pod-name-similarity.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pod Name Similarity 3 | author: Leo Tsaousis 4 | category: "Defense Evasion" 5 | description: | 6 | Deploy a backdoor container named to imitate system pods. 7 | 8 | System pods, created by controllers such as Deployments or DaemonSets have random suffixes in their names. Attackers can use this fact and name their backdoor pods as if they were created by the existing controllers to avoid detection. This can be attempted in the kube-system namespace alongside the other administrative containers. 9 | 10 | This test case creates pod imitating kube-proxy within the kube-system namespace, which is however based on a public image. 11 | mitre_ids: 12 | - T1036.005 13 | platform: kubernetes 14 | permissions: 15 | - namespaced: false 16 | apiGroups: [""] 17 | resources: 18 | - pods 19 | verbs: 20 | - create 21 | input_arguments: 22 | executors: 23 | sh: 24 | code: | 25 | kubectl -n kube-system run kube-proxy-bv61v --image ubuntu --command -- sleep infinity 26 | leonidas_kube: 27 | implemented: True 28 | detection: 29 | sigma_id: a80d927d-ac6e-443f-a867-e8d6e3897318 30 | status: experimental 31 | level: medium 32 | sources: 33 | - name: audit 34 | attributes: 35 | apiGroup: "" 36 | resource: pods 37 | verb: create 38 | namespace: kube-system 39 | references: 40 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Pod%20or%20container%20name%20similarily/ -------------------------------------------------------------------------------- /definitions/privilege_escalation/add-policy-to-iam-group.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add a policy to a group 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to add a policy to a group, in order to alter the permissions assigned to a user they have compromised. 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:AttachGroupPolicy 12 | input_arguments: 13 | group: 14 | description: Group to add policy to 15 | type: str 16 | value: NONE 17 | policyarn: 18 | description: Policy to add to group 19 | type: str 20 | value: arn:aws:iam::aws:policy/ReadOnlyAccess 21 | executors: 22 | sh: 23 | code: | 24 | aws iam attach-group-policy --group-name {{ group['value'] }} --policy-arn {{ policyarn['value'] }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].attach_group_policy( 31 | GroupName=group, 32 | PolicyArn=policyarn 33 | ) 34 | detection: 35 | sigma_id: 299b8380-8447-4f24-8520-c7a3c0008ef8 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: AttachGroupPolicy 42 | eventSource: "iam.amazonaws.com" 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 45 | -------------------------------------------------------------------------------- /generator/lib/sigmaexport.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code for generating the markdown used to build the documentation 3 | """ 4 | 5 | 6 | import os 7 | from collections import defaultdict 8 | 9 | 10 | class SigmaExport: 11 | """ 12 | Generates the Markdown documentation from the attack definitions 13 | """ 14 | 15 | def __init__(self, config, env): 16 | self.templates = {} 17 | self.config = config 18 | self.env = env 19 | 20 | def export_sigma(self, definitions, outdir): 21 | sigma_cases = defaultdict(lambda: defaultdict(list)) 22 | if not os.path.exists(outdir): 23 | os.makedirs(outdir) 24 | for category in definitions.categories: 25 | cat_outdir = os.path.join(outdir, category.replace(" ", "_").lower()) 26 | if not os.path.exists(cat_outdir): 27 | os.makedirs(cat_outdir) 28 | 29 | for case in definitions.case_set: 30 | if case["category"] == category: 31 | sigma_cases[category][case["name"]] = case 32 | 33 | for technique in sigma_cases[category]: 34 | sigma_filename = technique.replace(" ", "_").lower() + ".yml" 35 | filename = os.path.join(cat_outdir, sigma_filename) 36 | with open(filename, "w") as sigmaoutfile: 37 | sigmaoutfile.write(sigma_cases[category][technique]["sigma"]) 38 | print("Generated {} sigma definitions".format(len(definitions.case_set))) 39 | -------------------------------------------------------------------------------- /definitions/credential_access/app-creds-configmaps.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Access Application Credentials from ConfigMaps 3 | author: Leo Tsaousis 4 | category: "Credential Access" 5 | description: | 6 | Attempt to Access Application Credentials by listing ConfigMaps 7 | 8 | Despite this being a bad practice, Developers sometimes store secrets in the Kubernetes configuration files, such as environment variables in the pod configuration. Access to those configurations can be obtained by querying the API server. 9 | 10 | This test case only simulates a standard "list" operation, although the same result can also be achieved with a "watch" operation. The associated detection shall therefore not be considered complete, but only a 1-to-1 match of this particular test case. 11 | mitre_ids: 12 | - T1552.007 13 | platform: kubernetes 14 | permissions: 15 | - namespaced: true 16 | apiGroups: [""] 17 | resources: 18 | - configmaps 19 | verbs: 20 | - list 21 | input_arguments: 22 | executors: 23 | sh: 24 | code: | 25 | kubectl get configmaps 26 | leonidas_kube: 27 | implemented: True 28 | detection: 29 | sigma_id: 8235adde-cbe2-4cc0-a34d-1e8f0f068e48 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: audit 34 | attributes: 35 | apiGroup: "" 36 | resource: configmaps 37 | verb: list 38 | references: 39 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Application%20credentials%20in%20configuration%20files/ 40 | -------------------------------------------------------------------------------- /definitions/execution/create-pod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create pod 3 | author: Leo Tsaousis 4 | category: "Execution" 5 | description: | 6 | Deploy a malicious container. 7 | 8 | For this test case, the example image for the rogue container is fetched from a public repository, however rogue containers may use existing images for alternative purposes. 9 | mitre_ids: 10 | - T1204.003 11 | - T1578.002 12 | platform: kubernetes 13 | permissions: 14 | - namespaced: true 15 | apiGroups: [""] 16 | resources: 17 | - pods 18 | verbs: 19 | - create 20 | input_arguments: 21 | podname: 22 | description: Name of the pod to be created 23 | type: str 24 | value: "leonidas-netutils-pod" 25 | imagename: 26 | description: Name of the image to be used 27 | type: str 28 | value: "skybound/net-utils" 29 | command: 30 | description: Command to execute within the new pod 31 | type: str 32 | value: "sleep 3600" 33 | executors: 34 | sh: 35 | code: | 36 | kubectl run {{ podname }} --image {{ imagename }} --command -- {{ command }} 37 | leonidas_kube: 38 | implemented: True 39 | detection: 40 | sigma_id: 3c23ed24-51d0-4e29-bfa7-4ad26eaa27cd 41 | status: experimental 42 | level: low 43 | sources: 44 | - name: audit 45 | attributes: 46 | apiGroup: "" 47 | resource: pods 48 | subresource: "" 49 | verb: create 50 | references: 51 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/New%20Container/ -------------------------------------------------------------------------------- /definitions/privilege_escalation/update-inline-policy-for-user.yml: -------------------------------------------------------------------------------- 1 | name: Update Inline Policy for User 2 | author: Nick Jones 3 | description: | 4 | An adversary may attempt to update the inline policy set on an IAM user, in order to alter the permissions assigned to a user they have compromised. 5 | platform: aws 6 | category: Privilege Escalation 7 | mitre_ids: 8 | - T1098 9 | permissions: 10 | - iam:PutUserPolicy 11 | input_arguments: 12 | user: 13 | description: user to add policy to 14 | type: str 15 | value: NONE 16 | policyname: 17 | description: name of new inline policy 18 | type: str 19 | value: ExamplePolicy 20 | policydocument: 21 | description: file of new inline policy to set 22 | type: str 23 | value: file://examplepolicy.json 24 | executors: 25 | sh: 26 | code: | 27 | aws iam put-user-policy --user-name {{ user }} --policy-name {{ policyname }} --policy-document {{ policydocument }} 28 | leonidas_aws: 29 | implemented: False 30 | code: | 31 | raise NotImplementedErrror 32 | detection: 33 | sigma_id: 3f460fd0-f120-4c06-9365-140d1c4c8fda 34 | status: experimental 35 | level: low 36 | sources: 37 | - name: cloudtrail 38 | attributes: 39 | eventName: AttachGroupPolicy 40 | eventSource: "iam.amazonaws.com" 41 | falsepositives: 42 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/persistence/alter-assume-role-policy-document.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add an entity to an IAM role assumption policy 3 | author: Nick Jones 4 | description: | 5 | None 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:GetRole 12 | - iam:UpdateAssumeRolePolicy 13 | input_arguments: 14 | role: 15 | description: Name of role to alter 16 | type: str 17 | value: "OrganizationAccountAccessRole" 18 | entityarn: 19 | description: ARN of entity to add to the policy 20 | type: str 21 | value: "arn:aws:iam::000000000000:root" 22 | executors: 23 | sh: 24 | code: | 25 | aws -h 26 | leonidas_aws: 27 | implemented: true 28 | clients: 29 | - iam 30 | code: | 31 | role_data = clients["iam"].get_role(RoleName=role) 32 | assumeroledoc = role_data["Role"]["AssumeRolePolicyDocument"] 33 | assumeroledoc["Statement"][0]["Principal"]["AWS"].append(entityarn) 34 | result = clients["iam"].update_assume_role_policy(RoleName=role,PolicyDocument=json.dumps(assumeroledoc)) 35 | detection: 36 | sigma_id: 8dc9a4f7-ce41-4962-a2d2-5625d9e2502d 37 | status: experimental 38 | level: low 39 | sources: 40 | - name: cloudtrail 41 | attributes: 42 | eventName: None 43 | eventSource: None 44 | falsepositives: 45 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 46 | -------------------------------------------------------------------------------- /definitions/execution/exec-into-container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Exec into Container 3 | author: Leo Tsaousis 4 | category: "Execution" 5 | description: | 6 | Execute into a Pod's container 7 | 8 | Attackers who have permissions, can run malicious commands in a Pod's container within the cluster using "kubectl exec" command 9 | mitre_ids: 10 | - T1609 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: [""] 15 | resources: 16 | - pods/exec 17 | verbs: 18 | - create 19 | - namespaced: true 20 | apiGroups: [""] 21 | resources: 22 | - pods 23 | verbs: 24 | - get 25 | input_arguments: 26 | podname: 27 | description: Name of the pod to exec into 28 | type: str 29 | value: "vulnerable-pod" 30 | command: 31 | description: The command to execute within the pod. 32 | type: str 33 | value: "whoami" 34 | executors: 35 | sh: 36 | code: | 37 | kubectl exec {{ podname }} -- sh -c {{ command }} 38 | leonidas_kube: 39 | implemented: True 40 | detection: 41 | sigma_id: a1b0ca4e-7835-413e-8471-3ff2b8a66be6 42 | status: experimental 43 | level: low 44 | sources: 45 | - name: audit 46 | attributes: 47 | verb: create 48 | resource: pods 49 | subresource: exec 50 | falsepositives: 51 | - Legitimate debugging activity, investigate the identity performing the requests and their authorization 52 | references: 53 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Exec%20into%20container/ 54 | -------------------------------------------------------------------------------- /definitions/impact/delete-deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete deployment 3 | author: Leo Tsaousis 4 | category: "Impact" 5 | description: | 6 | Remove a deployment to impact business operations. 7 | 8 | The availability features of Kubernetes guarantee that workloads managed by collections such as Deployments or DaemonSets, will be automatically re-scheduled if terminated or deleted. Therefore, removing managed Pods will only incur temporary disruption. Determined actors aiming to cause Denial of Service will instead aim for controller objects like Deployments. 9 | mitre_ids: 10 | - T1485 11 | - MS-TA9038 12 | platform: kubernetes 13 | permissions: 14 | - namespaced: true 15 | apiGroups: 16 | - apps 17 | resources: 18 | - deployments 19 | verbs: 20 | - delete 21 | input_arguments: 22 | deploymentname: 23 | description: Name of the deployment to remove 24 | type: str 25 | value: "leonidas-netutils-deployment" 26 | executors: 27 | sh: 28 | code: | 29 | kubectl delete deployment {{ deploymentname }} 30 | leonidas_kube: 31 | implemented: True 32 | detection: 33 | sigma_id: 96047487-319b-4811-81d9-b9767a92aa5d 34 | status: experimental 35 | level: medium 36 | sources: 37 | - name: audit 38 | attributes: 39 | resource: deployments 40 | verb: delete 41 | references: 42 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Data%20destruction/ 43 | - https://www.crowdstrike.com/blog/crowdstrike-discovers-first-ever-dero-cryptojacking-campaign-targeting-kubernetes/ -------------------------------------------------------------------------------- /definitions/privilege_escalation/set-default-iam-policy-version.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change default policy version 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to change the default policy version of a policy to one that includes a different set of permissions 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:SetDefaultPolicyVersion 12 | input_arguments: 13 | policy_arn: 14 | description: ARN of the policy to create a new version for 15 | type: str 16 | value: "arn:aws:iam::123456789012:policy/test" 17 | policy_version: 18 | description: Version of the policy to set as default 19 | type: str 20 | value: "v2" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam set-default-policy-version –policy-arn policy_arn –version-id policy_version 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].set_default_policy_version( 31 | PolicyArn=policy_arn, 32 | VersionId=policy_version 33 | ) 34 | detection: 35 | sigma_id: 089c1b6a-1d77-4071-aac7-c91488ad88d5 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: SetDefaultPolicyVersion 42 | eventSource: iam.amazonaws.com 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 45 | -------------------------------------------------------------------------------- /generator/templates/markdown-kubernetes.jinja2: -------------------------------------------------------------------------------- 1 | # {{ case['name'] }} 2 | 3 | | Platform | Author | Last Update | 4 | | ---------------------- | -------------------- | --------------------------- | 5 | | {{ case["platform"] }} | {{ case["author"] }} | {{ case["last_modified"] }} | 6 | 7 | {{ case['description'] | trim}} 8 | 9 | ## MITRE IDs 10 | {% for id in case["mitre_ids"] %} 11 | * [{{ id }}](https://attack.mitre.org/techniques/{{ id }}/){% endfor %} 12 | 13 | ## Scope 14 | 15 | This test case {% if case['clusterwide'] %}needs{% else %}does not need{% endif %} Cluster-wide permissions 16 | 17 | ## Required Permissions 18 | 19 | ```yaml 20 | {{ case['permissions'] | to_nice_yaml }} 21 | ``` 22 | 23 | ## Required Parameters 24 | {% if case['input_arguments'] %} 25 | | Name | Type | Description | Example Value | 26 | | ---------- | --------------------- | ---------------------------- | ---------------------- | 27 | {% for name, details in case['input_arguments'].items() %}| {{ name }} | {{ details['type'] }} | {{ details['description'] }} | {{ details['value'] }} | 28 | {% endfor %}{% else %} 29 | None{% endif %} 30 | ## Attacker Action 31 | 32 | ```bash 33 | {{ case['compiled_command'] }} 34 | ``` 35 | 36 | 37 | ## Detection Case 38 | 39 | ### ELK query 40 | 41 | When logs are ingested into ELK, the following query can be used to identify relevant events. 42 | 43 | ``` 44 | {{ case['lucene_query'] }} 45 | ``` 46 | 47 | ### Sigma Definition 48 | 49 | ```yaml 50 | {{ case["sigma"] }} 51 | ``` -------------------------------------------------------------------------------- /definitions/persistence/update-login-profile-for-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update login profile for existing user 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to maintain access by updating an existing user's login profile, allowing them to authenticate to the AWS console with a password of their choice. 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:UpdateLoginProfile 12 | input_arguments: 13 | user: 14 | description: IAM user to update the login profile for 15 | type: str 16 | value: "root" 17 | password: 18 | description: Password to configure for login profile 19 | type: str 20 | value: "@#$%^&*()TestPass1234567890" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam update-login-profile -user-name user -password password -no-password-reset-required 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].update_login_profile( 31 | UserName=user, 32 | Password=password, 33 | PasswordResetRequired=False 34 | ) 35 | detection: 36 | sigma_id: 1fd04a6c-cf1f-4169-a9aa-1fd495f99930 37 | status: experimental 38 | level: low 39 | sources: 40 | - name: cloudtrail 41 | attributes: 42 | eventName: UpdateLoginProfile 43 | eventSource: iam.amazonaws.com 44 | falsepositives: 45 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/persistence/create-login-profile-for-iam-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create login profile for existing user 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to maintain access by adding a login profile to a user that does not have one configured, allowing them to authenticate to the AWS console with a password of their choice 6 | platform: aws 7 | category: Persistence 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:CreateLoginProfile 12 | input_arguments: 13 | user: 14 | description: IAM user to create the login profile for 15 | type: str 16 | value: "root" 17 | password: 18 | description: Password to configure for login profile 19 | type: str 20 | value: "TestPass1234567890" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam create-login-profile --user-name {{ user }} --password {{ password }} --no-password-reset-required 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].create_login_profile( 31 | UserName=user, 32 | Password=password, 33 | PasswordResetRequired=False 34 | ) 35 | detection: 36 | sigma_id: e367ad8f-0173-4cb3-8f1a-9b76b69b9de1 37 | status: experimental 38 | level: low 39 | sources: 40 | - name: cloudtrail 41 | attributes: 42 | eventName: CreateLoginProfile 43 | eventSource: iam.amazonaws.com 44 | falsepositives: 45 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 46 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/create-iam-policy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create Policy 3 | author: Nick Jones 4 | description: | 5 | An attacker may attempt to create a new version of a given IAM policy in order to attach extra permissions to an entity they control. 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:CreatePolicy 12 | input_arguments: 13 | policy_name: 14 | description: ARN of the policy to create a new version for 15 | type: str 16 | value: "arn:aws:iam::123456789012:policy/test" 17 | policy_document: 18 | description: New policy to upload - for the CLI, this should be a path to the json document. For Leonidas, this should be the JSON document itself. 19 | type: str 20 | value: "file://path/to/administrator/policy.json" 21 | executors: 22 | sh: 23 | code: | 24 | aws iam create-policy --policy-name {{ policy_name }} --policy-document {{ policy_document }} 25 | leonidas_aws: 26 | implemented: True 27 | clients: 28 | - iam 29 | code: | 30 | result = clients["iam"].create_policy( 31 | PolicyName=policy_arn, 32 | PolicyDocument=policy_document 33 | ) 34 | detection: 35 | sigma_id: 1352e02d-4207-4709-8980-b3a08f346c6d 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: CreatePolicy 42 | eventSource: iam.amazonaws.com 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 45 | -------------------------------------------------------------------------------- /generator/templates/kube-resources.jinja2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: leonidas-deployment 5 | namespace: {{ namespace }} 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: leonidas 10 | template: 11 | metadata: 12 | labels: 13 | app: leonidas 14 | spec: 15 | serviceAccount: leonidas-sa 16 | volumes: 17 | - name: varlog 18 | emptyDir: {} 19 | containers: 20 | - name: leonidas-container 21 | image: {{ image_url }} 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 5000 25 | name: flask-port 26 | volumeMounts: 27 | - name: varlog 28 | mountPath: /var/log 29 | - name: log-sidecar 30 | image: busybox:1.28 31 | args: [/bin/sh, -c, 'tail -n+1 -F /var/log/leonidas-flask.log'] 32 | volumeMounts: 33 | - name: varlog 34 | mountPath: /var/log 35 | 36 | --- 37 | apiVersion: v1 38 | kind: ServiceAccount 39 | metadata: 40 | namespace: {{ namespace }} 41 | name: leonidas-sa 42 | --- 43 | kind: {% if is_namespaced %}RoleBinding{% else %}ClusterRoleBinding{% endif %} 44 | apiVersion: rbac.authorization.k8s.io/v1 45 | metadata: 46 | name: leonidas-{% if is_namespaced %}role{% else %}clusterrole{% endif %}binding 47 | {% if is_namespaced %}namespace: {{ namespace }}{% endif %} 48 | subjects: 49 | - kind: ServiceAccount 50 | name: leonidas-sa 51 | namespace: {{ namespace }} 52 | roleRef: 53 | kind: {% if is_namespaced %}Role{% else %}ClusterRole{% endif %} 54 | name: leonidas-{% if is_namespaced %}role{% else %}clusterrole{% endif %} 55 | apiGroup: "" 56 | --- -------------------------------------------------------------------------------- /definitions/credential_access/app-creds-env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Application Credentials from Environment Variables 3 | author: Leo Tsaousis 4 | category: "Credential Access" 5 | description: | 6 | Attempt to Access Application Credentials in Environmemt Variables 7 | 8 | Developers store secrets in the Kubernetes configuration files, such as environment variables in the pod configuration. These variables can be listed within the description of pods. 9 | 10 | This test case only simulates a standard "list" operation, although the same result can also be achieved with a "watch" operation. The associated detection shall therefore not be considered complete, but only a 1-to-1 match of this particular test case. 11 | mitre_ids: 12 | - T1552.007 13 | platform: kubernetes 14 | permissions: 15 | - namespaced: true 16 | apiGroups: [""] 17 | resources: 18 | - pods 19 | verbs: 20 | - list 21 | input_arguments: 22 | executors: 23 | sh: 24 | code: | 25 | kubectl get pods -o=jsonpath="{.items[*].spec.containers[*].env}" 26 | leonidas_kube: 27 | implemented: True 28 | detection: 29 | sigma_id: ec8ec8b1-c696-4e9a-ae20-8e1c1f056b09 30 | status: experimental 31 | level: low 32 | sources: 33 | - name: audit 34 | attributes: 35 | apiGroup: "" 36 | resource: pods 37 | verb: list 38 | falsepositives: 39 | - get pods might be performed for various legitimate reasons. Stronger detections could be based on a correlation search for subsequent activity making use of environment variables 40 | references: 41 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Application%20credentials%20in%20configuration%20files/ -------------------------------------------------------------------------------- /generator/templates/kubernetes/sigma.jinja2: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ name }} 3 | id: {{ detection.sigma_id }} 4 | status: {{ detection.status }} 5 | author: {{ author }} 6 | date: {{ last_modified }} 7 | description: | 8 | {{ description | trim | indent(2) }} 9 | logsource: 10 | product: kubernetes 11 | service: {{ detection.sources[0]["name"] }} 12 | detection: 13 | selection: 14 | verb: {{ detection["sources"][0]["attributes"]["verb"] }} 15 | {% if detection["sources"][0]["attributes"]["apiGroup"] %}apiGroup: {{ detection["sources"][0]["attributes"]["apiGroup"] }}{%- endif %} 16 | resource: {{ detection["sources"][0]["attributes"]["resource"] }} 17 | {% if detection["sources"][0]["attributes"]["subresource"] -%} 18 | subresource: {{ detection["sources"][0]["attributes"]["subresource"] }}{%- endif -%} 19 | {%- if detection["sources"][0]["attributes"]["namespace"] -%} 20 | namespace: {{ detection["sources"][0]["attributes"]["namespace"] }}{%- endif -%} 21 | {%- if detection["sources"][0]["attributes"]["capabilities"] -%} 22 | capabilities: "{{ detection["sources"][0]["attributes"]["capabilities"] }}"{%- endif -%} 23 | {%- if detection["sources"][0]["attributes"]["hostPath"] -%} 24 | hostPath: "{{ detection["sources"][0]["attributes"]["hostPath"] }}"{%- endif %} 25 | condition: selection 26 | level: {{ detection["level"] }} 27 | {% if mitre_ids %}tags: 28 | {%- for id in mitre_ids %} 29 | - attack.{{ id }} 30 | {%- endfor %}{%- endif %} 31 | {% if references %}references:{% for reference in references %} 32 | - {{ reference }}{% endfor %}{% endif %} 33 | {% if detection.falsepositives %}falsepositives:{% for fp in detection.falsepositives %} 34 | - {{ fp }}{% endfor %}{% endif %} 35 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/create-iam-policy-version.yml: -------------------------------------------------------------------------------- 1 | name: Create New Policy Version 2 | author: Nick Jones 3 | description: | 4 | An attacker may attempt to create a new version of a given IAM policy in order to attach extra permissions to an entity they control. 5 | platform: aws 6 | category: Privilege Escalation 7 | mitre_ids: 8 | - T1098 9 | permissions: 10 | - iam:CreatePolicyVersion 11 | input_arguments: 12 | policy_arn: 13 | description: ARN of the policy to create a new version for 14 | type: str 15 | value: "arn:aws:iam::123456789012:policy/test" 16 | policy_document: 17 | description: New policy to upload - for the CLI, this should be a path to the json document. For Leonidas, this should be the JSON document itself. 18 | type: str 19 | value: "file://path/to/administrator/policy.json" 20 | executors: 21 | sh: 22 | code: | 23 | aws iam create-policy-version –policy-arn policy_arn –policy-document policy_document –set-as-default 24 | leonidas_aws: 25 | implemented: True 26 | clients: 27 | - iam 28 | code: | 29 | result = clients["iam"].create_policy_version( 30 | PolicyArn=policy_arn, 31 | PolicyDocument=policy_document, 32 | SetAsDefault=True 33 | ) 34 | detection: 35 | sigma_id: b5104c3a-40f4-464a-934a-a917a89faf1a 36 | status: experimental 37 | level: low 38 | sources: 39 | - name: cloudtrail 40 | attributes: 41 | eventName: CreatePolicyVersion 42 | eventSource: iam.amazonaws.com 43 | falsepositives: 44 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. -------------------------------------------------------------------------------- /definitions/defense_evasion/update-guardduty-ip-set.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update guardduty ip set 3 | author: Nick Jones 4 | description: | 5 | An adversary may attempt to alter a configured GuardDuty IP whitelist in order to whitelist systems they control and reduce the chance of malicious activity being detected. 6 | platform: aws 7 | category: Defense Evasion 8 | mitre_ids: 9 | - T1562 10 | permissions: 11 | - guardduty:UpdateIPSet 12 | input_arguments: 13 | detectorid: 14 | description: ID of the guardduty detector associated with the IP set list 15 | type: str 16 | value: "12345" 17 | ipsetid: 18 | description: ID of the IP set to be updated 19 | type: str 20 | value: "12345" 21 | location: 22 | description: Location of the IP whitelist 23 | type: str 24 | value: "http://www.example.com" 25 | executors: 26 | sh: 27 | code: | 28 | aws guardduty update-ip-set --activate --detector-id {{ detectorid }} --ip-set-id {{ ipsetid }} --location {{ location }} 29 | leonidas_aws: 30 | implemented: True 31 | clients: 32 | - guardduty 33 | code: | 34 | result = clients['guardduty'].update_ip_set( 35 | Activate=True, 36 | DetectorId=detectorid, 37 | IpSetId=ipsetid, 38 | Location=location 39 | ) 40 | detection: 41 | sigma_id: 2faecc34-b0cb-4d41-872d-85186b6c2c6c 42 | status: experimental 43 | level: low 44 | sources: 45 | - name: "cloudtrail" 46 | attributes: 47 | eventName: "UpdateIPSet" 48 | eventSource: "*.guardduty.amazonaws.com" 49 | falsepositives: 50 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 51 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/attach-malicious-lambda-layer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Attach a Malicious Lambda Layer 3 | author: Nick Jones 4 | description: | 5 | An attacker may attach a Lambda layer to an existing function to override a library that is used by the function, and use that malicious code to execute AWS API calls with that functions function's IAM role. 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1525 10 | permissions: 11 | - lambda:UpdateFunctionConfiguration 12 | input_arguments: 13 | functionname: 14 | description: Name of the function to be targeted 15 | type: str 16 | value: "example-function" 17 | layers: 18 | description: List of layers to add as space-separated ARNs 19 | type: str 20 | value: "arn:aws:lambda:us-east-1:123456789012:layer:my-layer" 21 | executors: 22 | sh: 23 | code: | 24 | aws lambda update-function-configuration --function-name {{ functionname }} --layers {{ layers }} 25 | leonidas_aws: 26 | implemented: False 27 | code: | 28 | raise NotImplementedError 29 | detection: 30 | sigma_id: 8fb105ea-19f8-4537-965a-cdc68200b8d9 31 | status: experimental 32 | level: medium 33 | sources: 34 | - name: cloudtrail 35 | attributes: 36 | eventName: UpdateFunctionConfiguration 37 | eventSource: lambda.amazonaws.com 38 | falsepositives: 39 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 40 | references: 41 | - https://docs.aws.amazon.com/lambda/latest/dg/API_UpdateFunctionConfiguration.html 42 | - https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-configuration.html 43 | -------------------------------------------------------------------------------- /definitions/defense_evasion/add-new-guardduty-ip-set.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add new guardduty ip set 3 | author: Nick Jones 4 | description: "An adversary may attempt to add a new GuardDuty IP whitelist in order to whitelist systems they control and reduce the chance of malicious activity being detected." 5 | platform: aws 6 | category: Defense Evasion 7 | mitre_ids: 8 | - T1562 9 | permissions: 10 | - guardduty:CreateIPSet 11 | input_arguments: 12 | detectorid: 13 | description: ID of the guardduty detector associated with the IP set list 14 | type: str 15 | value: "12345" 16 | format: 17 | description: Format of the new IP set list - choice of TXT, STIX, OTX_CSV, ALIEN_VAULT, PROOF_POINT, FIRE_EYE 18 | type: str 19 | value: "TXT" 20 | location: 21 | description: Location of the IP whitelist 22 | type: str 23 | value: "http://www.example.com" 24 | executors: 25 | sh: 26 | code: | 27 | aws guardduty create-ip-set --activate --detector-id {{ detectorid }} --format {{ format }} --location {{ location }} 28 | leonidas_aws: 29 | implemented: True 30 | clients: 31 | - guardduty 32 | code: | 33 | result = clients['guardduty'].create_ip_set( 34 | Activate=True, 35 | DetectorId=detectorid, 36 | Location=location, 37 | Format=format 38 | ) 39 | detection: 40 | sigma_id: faf89476-061a-4c29-8f9c-2ed65e65de2e 41 | status: experimental 42 | level: low 43 | sources: 44 | - name: "cloudtrail" 45 | attributes: 46 | eventName: "CreateIPSet" 47 | eventSource: "*.guardduty.amazonaws.com" 48 | falsepositives: 49 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 50 | -------------------------------------------------------------------------------- /definitions/execution/sidecar-injection.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sidecar Injection 3 | author: Leo Tsaousis 4 | category: "Execution" 5 | description: | 6 | Inject a sidecar container into a running deployment 7 | 8 | A sidecar container is an additional container that resides alongside the main container within the pod. Containers can be added to running resources like Deployments/DeamonSets/StatefulSets by means of "kubectl patch". By injecting a new container within a legitimate pod attackers can run their code and hide their activity, instead of running their own separated pod in the cluster. 9 | mitre_ids: 10 | - T609 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: 15 | - apps 16 | resources: 17 | - deployments 18 | verbs: 19 | - get 20 | - patch 21 | input_arguments: 22 | deployment: 23 | description: Name of the deployment to patch 24 | type: str 25 | value: "patchable-deployment" 26 | custom_yaml: 27 | type: file 28 | description: YAML definition of the container template that will be patched into the deployment 29 | value: 30 | spec: 31 | template: 32 | spec: 33 | containers: 34 | - name: injected-sidecar 35 | image: skybound/net-utils 36 | command: 37 | - sleep 38 | - "3600" 39 | executors: 40 | sh: 41 | code: | 42 | kubectl patch deployment {{ deployment }} --patch-file /tmp/custom.yml 43 | leonidas_kube: 44 | implemented: True 45 | detection: 46 | level: low 47 | sigma_id: ad9012a6-e518-4432-9890-f3b82b8fc71f 48 | status: experimental 49 | sources: 50 | - name: audit 51 | attributes: 52 | apiGroup: apps 53 | resource: deployments 54 | verb: patch 55 | references: 56 | - https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch 57 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Sidecar%20Injection/ 58 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/privileged-container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Privileged Container 3 | author: Leo Tsaousis 4 | category: "Privilege Escalation" 5 | description: | 6 | Create a privileged container 7 | 8 | A privileged container is a container that can access the host with all of the root capabilities of the host machine. This allows it to view, interact and modify processes, network operations, IPC calls, the file system, mount points, SELinux configurations etc. as the root user on the host. 9 | mitre_ids: 10 | - T611 11 | platform: kubernetes 12 | permissions: 13 | - namespaced: true 14 | apiGroups: [""] 15 | resources: 16 | - pods 17 | verbs: 18 | - create 19 | - get 20 | input_arguments: 21 | custom_yaml: 22 | description: YAML manifest for the pod - leave this empty to use the default spec that performs the TTP 23 | type: file 24 | value: 25 | apiVersion: v1 26 | kind: Pod 27 | metadata: 28 | name: privileged-ubuntu 29 | labels: 30 | app: privileged-ubuntu 31 | spec: 32 | containers: 33 | - image: ubuntu 34 | name: privileged-ubuntu 35 | command: ["/bin/sh", "-c", "sleep infinity"] 36 | securityContext: 37 | privileged: true 38 | runAsUser: 0 39 | executors: 40 | sh: 41 | code: | 42 | kubectl -f /tmp/custom.yml apply 43 | leonidas_kube: 44 | implemented: True 45 | detection: 46 | sigma_id: c5cd1b20-36bb-488d-8c05-486be3d0cb97 47 | status: experimental 48 | level: low 49 | sources: 50 | - name: audit 51 | attributes: 52 | verb: create 53 | apiGroup: "" 54 | resource: pods 55 | privileged: True # this requires updating of the SigmaHQ/pySigma-backend-elasticsearch project to map 56 | # privileged=kubernetes.audit.responseObject.spec.containers.securityContext.privileged 57 | references: 58 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Privileged%20container/ 59 | - https://kubenomicon.com/Privilege_escalation/Privileged_container.html 60 | -------------------------------------------------------------------------------- /sigma-pipeline-kubernetes-to-elk.yml: -------------------------------------------------------------------------------- 1 | name: Mapping of Kubernetes test cases for ELK's default Kubernetes integration 2 | priority: 30 3 | transformations: 4 | 5 | # Step 1 - checks for performance 6 | 7 | - id: index_condition 8 | type: add_condition 9 | # only search upon Kubernetes audit logs 10 | conditions: 11 | kubernetes.audit.kind: Event 12 | # only transform fields if this is a Kubernetes rule 13 | rule_conditions: 14 | - type: logsource 15 | product: kubernetes 16 | 17 | # Step 2 - the transformations 18 | 19 | 20 | # Map simplified Sigma fields to the names ELK assigns them 21 | - id: field_mapping 22 | type: field_name_mapping 23 | mapping: 24 | verb: 25 | - kubernetes.audit.verb 26 | apiGroup: 27 | - kubernetes.audit.objectRef.apiGroup 28 | resource: 29 | - kubernetes.audit.objectRef.resource 30 | subresource: 31 | - kubernetes.audit.objectRef.subresource 32 | namespace: 33 | - kubernetes.audit.objectRef.namespace 34 | capabilities: 35 | - kubernetes.audit.requestObject.spec.containers.securityContext.capabilities.add 36 | hostPath: 37 | - kubernetes.audit.requestObject.spec.volumes.hostPath 38 | 39 | # If apiGroup is "" OR omitted, then drop from query, as the ELK Kubernetes integration doesn't set this event field when apiGroup is the default 40 | - id: drop_default_apigroup 41 | type: drop_detection_item 42 | field_name_conditions: 43 | - type: include_fields 44 | fields: 45 | - apiGroup 46 | - kubernetes.audit.objectRef.apiGroup 47 | detection_item_conditions: 48 | - type: match_string 49 | cond: any 50 | pattern: "^$" 51 | 52 | # If subresource is "" OR omitted, then drop from query, as the ELK Kubernetes integration doesn't set this event field for resource-only endpoints 53 | - id: drop_empty_subresource 54 | type: drop_detection_item 55 | field_name_conditions: 56 | - type: include_fields 57 | fields: 58 | - subresource 59 | - kubernetes.audit.objectRef.subresource 60 | detection_item_conditions: 61 | - type: match_string 62 | cond: any 63 | pattern: "^$" 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /jupyter/lib/leoclientlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Leo - case executor for Leonidas. Takes a config file as its first argument. 5 | """ 6 | 7 | 8 | import datetime 9 | import json 10 | # import logging 11 | import sys 12 | import time 13 | 14 | import requests 15 | import yaml 16 | 17 | class Client(object): 18 | def __init__(self, url, apikey = None, casefile = "./caseconfig.yml"): 19 | self.url = url 20 | self.apikey = apikey 21 | self.cases = self.load_cases(casefile) 22 | 23 | def load_cases(self, casefile): 24 | config = yaml.safe_load(open(casefile, "r")) 25 | return config["cases"] 26 | 27 | def get_identity(self, creds): 28 | ret_value = {} 29 | if ("role_arn" in creds) and (creds["role_arn"] != ""): 30 | ret_value["role_arn"] = creds["role_arn"] 31 | elif ("access_key_id" in creds) and (creds["secret_access_key"] != ""): 32 | ret_value["access_key_id"] = creds["access_key_id"] 33 | ret_value["secret_access_key"] = creds["secret_access_key"] 34 | if ("region" in creds) and (creds["region"] != ""): 35 | ret_value["region"] = creds["region"] 36 | return ret_value 37 | 38 | def run_case(self, path, args = {}, creds = {}): 39 | case_url = self.url + path 40 | # if we're running Leonidas locally, no need for an API key, 41 | # so let's not error out if there's not one in the config 42 | headers = {} 43 | try: 44 | headers["x-api-key"] = self.apikey 45 | except Exception as e: 46 | print(e) 47 | # If it's a request with parameters it'll need to be POSTed, otherwise it's a GET request 48 | if (args != {}) and (args != None): 49 | args.update(self.get_identity(creds)) 50 | r = requests.post(case_url, headers=headers, params=args) 51 | else: 52 | args.update(self.get_identity(creds)) 53 | r = requests.get(case_url, headers=headers, params=args) 54 | print(json.dumps(r.json(), indent=4, sort_keys=True)) 55 | return r.json() 56 | -------------------------------------------------------------------------------- /output/leonidas/api/api_base.py: -------------------------------------------------------------------------------- 1 | import logging.config 2 | 3 | import click 4 | from flask import Flask 5 | from flask_restx import Api 6 | 7 | from .utils import Config 8 | 9 | # Configure logger for events, to go into stdout for accessing through primary container (easy on kubectl logs) 10 | # separating them from Flask console output (root logger) which go to a file, to be picked up by the sidecar container 11 | # - This does not affect AWS Python code, Jinja template does not use Python logging but just print() 12 | try: 13 | logging.config.dictConfig( 14 | { 15 | "version": 1, 16 | "formatters": { 17 | "eventFmt":{ 18 | "format":"%(message)s" 19 | } 20 | }, 21 | "handlers": { 22 | "consoleHandler":{ 23 | "class": "logging.StreamHandler", 24 | "formatter": "eventFmt", 25 | }, 26 | "fileHandler":{ 27 | "class": "logging.FileHandler", 28 | "filename": "/var/log/leonidas-flask.log", 29 | } 30 | }, 31 | "root": {"level": "INFO", "handlers": ["fileHandler"]}, 32 | 33 | "loggers": { 34 | "events": { 35 | "level": "INFO", 36 | "handlers": ["consoleHandler"], 37 | "propagate": False, 38 | } 39 | }, 40 | } 41 | ) 42 | except: 43 | pass 44 | 45 | def secho(text, file=None, nl=None, err=None, color=None, **styles): 46 | pass 47 | 48 | def echo(text, file=None, nl=None, err=None, color=None, **styles): 49 | pass 50 | 51 | click.echo = echo 52 | click.secho = secho 53 | # This needed to hide Flask's startup message and keep stdout clean from messages 54 | # that aren't events, and therefore jq-friendly 55 | # https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server 56 | 57 | app = Flask(__name__) 58 | app.config.from_object(Config) 59 | api = Api( 60 | app, 61 | version="2.0", 62 | title="Leonidas", 63 | description="An API for executing attacker actions within AWS and Kubernetes", 64 | ) 65 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/writeable-hostpath-mount.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Writeable hostPath Mount 3 | author: Leo Tsaousis 4 | category: "Privilege Escalation" 5 | description: | 6 | Create a container with a writeable hostPath mount 7 | 8 | A hostPath volume mounts a directory or a file from the node to the container. Attackers who have permissions to create a new pod in the cluster may create one with a writable hostPath volume and chroot to escape to the underlying node. 9 | 10 | This test case simulates the first step of this attack, by creating a pod with a hostPath mount. 11 | mitre_ids: 12 | - T611 13 | platform: kubernetes 14 | permissions: 15 | - namespaced: true 16 | apiGroups: [""] 17 | resources: 18 | - pods 19 | verbs: 20 | - create 21 | - get 22 | input_arguments: 23 | custom_yaml: 24 | description: YAML manifest for the pod - leave this empty to use the default spec that performs the TTP 25 | type: file 26 | value: 27 | apiVersion: v1 28 | kind: Pod 29 | metadata: 30 | labels: 31 | run: attacker-pod 32 | name: attacker-pod 33 | spec: 34 | volumes: 35 | - name: host-fs 36 | hostPath: 37 | path: / 38 | containers: 39 | - image: ubuntu 40 | name: attacker-pod 41 | command: ["/bin/sh", "-c", "sleep infinity"] 42 | volumeMounts: 43 | - name: host-fs 44 | mountPath: /host 45 | executors: 46 | sh: 47 | code: | 48 | kubectl -f /tmp/custom.yml apply 49 | leonidas_kube: 50 | implemented: True 51 | detection: 52 | sigma_id: 402b955c-8fe0-4a8c-b635-622b4ac5f902 53 | status: experimental 54 | level: low 55 | sources: 56 | - name: audit 57 | attributes: 58 | apiGroup: "" 59 | resource: pods 60 | verb: create 61 | hostPath: '*' 62 | falsepositives: 63 | - Various legitimate reasons exist for using hostPath mounts, such as running containers that need node-level access to e.g. transfer logs to a central location, or exposing host configuration files to static pods 64 | references: 65 | - https://microsoft.github.io/Threat-Matrix-for-Kubernetes/techniques/Writable%20hostPath%20mount/ 66 | - https://kubenomicon.com/Persistence/Writable_hostPath_mount.html 67 | -------------------------------------------------------------------------------- /definitions/privilege_escalation/add-role-to-new-ec2-instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add an existing role to a new EC2 instance 3 | author: Nick Jones 4 | description: | 5 | An adversary may attach an existing role to a new EC2 instance to which they have access 6 | platform: aws 7 | category: Privilege Escalation 8 | mitre_ids: 9 | - T1098 10 | permissions: 11 | - iam:PassRole 12 | - ec2:RunInstances 13 | input_arguments: 14 | image_id: 15 | description: AMI to create instance from 16 | type: str 17 | value: ami-a4dc46db 18 | instance_type: 19 | description: Type of instance to create 20 | type: str 21 | value: t2.micro 22 | iam_instance_profile_name: 23 | description: EC2 instance profile to assign 24 | type: str 25 | value: ec2-instance-profile 26 | key_name: 27 | description: Name of SSH key to assign to instance 28 | type: str 29 | value: my-ssh-key 30 | security_group_id: 31 | description: ID of a security group to apply to the instance 32 | type: str 33 | value: sg-123456 34 | executors: 35 | sh: 36 | code: | 37 | aws ec2 run-instances –image-id image_id –instance-type instance_type –iam-instance-profile Name=iam_instance_profile_name –key-name key_name –security-group-ids security_group_ids 38 | leonidas_aws: 39 | implemented: True 40 | clients: 41 | - ec2 42 | code: | 43 | result = clients['ec2'].run_instances( 44 | ImageId=image_id, 45 | InstanceType=instance_type, 46 | KeyName=key_name, 47 | MaxCount=1, 48 | MinCount=1, 49 | Monitoring={ 50 | 'Enabled': True|False 51 | }, 52 | SecurityGroupIds=[ 53 | security_group_id 54 | ], 55 | ClientToken='str', 56 | EbsOptimized=True|False, 57 | IamInstanceProfile={ 58 | 'Name': iam_instance_profile_name 59 | } 60 | ) 61 | detection: 62 | sigma_id: 899eb2b1-6e96-4203-bd38-9cddf970a50a 63 | status: experimental 64 | level: low 65 | sources: 66 | - name: cloudtrail 67 | attributes: 68 | eventName: RunInstances 69 | eventSource: "ec2.amazonaws.com" 70 | falsepositives: 71 | - Developers making legitimate changes to the environment. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. 72 | -------------------------------------------------------------------------------- /leo/leo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Leo - case executor for Leonidas. Takes a config file as its first argument. 5 | """ 6 | 7 | 8 | import datetime 9 | import json 10 | # import logging 11 | import sys 12 | import time 13 | 14 | import requests 15 | import yaml 16 | 17 | if __name__ == "__main__": 18 | config = yaml.safe_load(open(sys.argv[1], "r")) 19 | print("Url: {}".format(config["url"])) 20 | for case in config["cases"]: 21 | print("[{0} UTC] {1}".format(datetime.datetime.utcnow(), config["cases"][case]["name"])) 22 | url = config["url"] + config["cases"][case]["path"] 23 | headers = {} 24 | # if we're running Leonidas locally, no need for an API key, 25 | # so let's not error out if there's not one in the config 26 | try: 27 | headers["x-api-key"] = config["apikey"] 28 | except KeyError: 29 | continue 30 | 31 | # Grab the args from the case 32 | if "args" in config["cases"][case]: 33 | if config["cases"][case]["args"]: 34 | args = config["cases"][case]["args"] 35 | else: 36 | args = {} 37 | else: 38 | args = {} 39 | 40 | # load any credentials and region details configured in the caseconfig 41 | if ("identity" in config) and (config["identity"] is not None): 42 | if "role_arn" in config["identity"]: 43 | args["role_arn"] = config["identity"]["role_arn"] 44 | elif ("access_key_id" in config["identity"]) and ("secret_access_key" in config["identity"]): 45 | args["access_key_id"] = config["identity"]["access_key_id"] 46 | args["secret_access_key"] = config["identity"]["secret_access_key"] 47 | if "region" in config["identity"]: 48 | args["region"] = config["identity"]["region"] 49 | 50 | # If it's a request with parameters it'll need to be POSTed, otherwise it's a GET request 51 | if ("args" in config["cases"][case]) and (config["cases"][case]["args"] is not None): 52 | r = requests.post(url, headers=headers, params=args) 53 | else: 54 | r = requests.get(url, headers=headers, params=args) 55 | print(json.dumps(r.json(), indent=4, sort_keys=True)) 56 | 57 | # Sleep between cases 58 | time.sleep(config["sleeptime"]) 59 | 60 | print("Ran {} test cases".format(len(config["cases"]))) 61 | -------------------------------------------------------------------------------- /generator/templates/aws_python_execution_function.jinja2: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | from flask import request 4 | from flask_restx import Resource, Namespace 5 | from .api_base import api, app 6 | from .utils import json_serial, get_clients, aws_define_identity 7 | 8 | {% for case in cases %} 9 | class {{ case['name']|title|replace(" ", "")|replace("-", "") }}(Resource): 10 | """ 11 | API Class for {{ case['name'] }} 12 | """ 13 | 14 | @api.doc(params={ {% if case['input_arguments'] %}{% for arg, contents in case['input_arguments'].items() %} 15 | '{{ arg }}': '{{ contents['description'] }}', {% endfor %}{% endif %} 16 | 'role_arn': 'ARN of a role to assume', 17 | 'access_key_id': 'Access key ID for an entity to execute the test case as. Must be combined with secret_access_key', 18 | 'secret_access_key': 'Secret access key to match the access_key_id', 19 | 'region': 'Region to use for the API call' 20 | } 21 | ) 22 | {% if case['input_arguments'] %}def post(self):{% else %}def get(self):{% endif %} 23 | """ 24 | {{ case['description']|wordwrap|indent }} 25 | """ 26 | {% if case['input_arguments'] %}{% for arg, contents in case['input_arguments'].items() %}{{ arg }} = request.args.get("{{ arg }}") or {% if "str" in contents['type'] %}"{{ contents['value'] }}"{% else %}{{ contents['value'] }}{% endif %} 27 | {% endfor %}{% endif %} 28 | identity = aws_define_identity(request) 29 | try: 30 | clients = get_clients(identity, {{ case["executors"]["leonidas_aws"]['clients'] }}) 31 | {{ case["executors"]["leonidas_aws"]["rendered"] |indent(width=12) }} 32 | except Exception as excpt: 33 | result = excpt 34 | event_dict = { 35 | "request": { 36 | "usecase": request.path, 37 | "args": request.args, 38 | "timestamp": datetime.datetime.now(tz=datetime.timezone.utc), 39 | "identity": identity 40 | }, 41 | "response": result 42 | } 43 | print(json.dumps(event_dict, default=json_serial)) 44 | return result 45 | {% endfor %} 46 | 47 | ns = Namespace('{{ category }}', description='{{ category|replace('_', ' ')|title }}') 48 | 49 | {% for case in cases %} 50 | ns.add_resource({{ case['name']|title|replace(" ", "")|replace("-", "") }}, '/{{ case['name']|replace(" ", "_")|replace("-", "")|lower }}'){% endfor %} 51 | api.add_namespace(ns) 52 | 53 | if __name__ == "__main__": 54 | app.run(debug=False) -------------------------------------------------------------------------------- /generator/lib/kubeapigen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code to generate the Leonidas API to be used within Kubernetes 3 | """ 4 | 5 | import os 6 | 7 | 8 | class KubeAPIGen: 9 | """ 10 | Generates the Flask API & Kubernetes resources 11 | """ 12 | 13 | def __init__(self, config, env): 14 | self.templates = {} 15 | self.env = env 16 | self.config = config 17 | if "namespace" not in config or config["namespace"] == "": 18 | self.config["namespace"] = "default" 19 | 20 | self.templates["kube_python_function"] = env.get_template( 21 | "kube_python_execution_function.jinja2" 22 | ) 23 | self.templates["api_core"] = env.get_template("python_api_core.jinja2") 24 | self.templates["serverless_config"] = env.get_template("serverless.jinja2") 25 | self.templates["kube_resources"] = env.get_template("kube-resources.jinja2") 26 | self.import_list = [] 27 | self.casecount = 0 28 | 29 | 30 | def generate_kube_resources(self, is_namespaced): 31 | """ 32 | Generate the Kubernetes resources for Leonidas 33 | """ 34 | return self.templates["kube_resources"].render( 35 | { 36 | "namespace": self.config["namespace"], 37 | "is_namespaced":is_namespaced, 38 | "image_url": self.config["image_url"], 39 | } 40 | ) 41 | 42 | def generate_python_api(self, outdir, definitions): 43 | """ 44 | Generate the flask API 45 | """ 46 | for category in definitions.categories: 47 | self.import_list.append(category.replace(" ", "_").lower()) 48 | self._generate_api_category(outdir, category, definitions) 49 | 50 | # API root file 51 | rendered = self.templates["api_core"].render({"categories": self.import_list}) 52 | filename = os.path.join(outdir, "leonidas.py") 53 | with open(filename, "w") as apioutfile: 54 | apioutfile.write(rendered) 55 | 56 | 57 | def _generate_api_category(self, outdir, category, definitions): 58 | """ 59 | Build a given category of test cases 60 | """ 61 | outdir = os.path.join(outdir, "api") 62 | if not os.path.exists(outdir): 63 | os.makedirs(outdir) 64 | category_case_set = [] 65 | for case in definitions.case_set: 66 | if case["category"] == category: 67 | try: 68 | if case["executors"]["leonidas_kube"]["implemented"]: 69 | category_case_set.append(case) 70 | self.casecount = self.casecount + 1 71 | except KeyError: 72 | continue 73 | rendered = self.templates["kube_python_function"].render( 74 | { 75 | "cases": category_case_set, 76 | "category": category.replace(" ", "_").lower(), 77 | } 78 | ) 79 | py_filename = category.replace(" ", "_").lower() + ".py" 80 | filename = os.path.join(outdir, py_filename) 81 | pyoutfile = open(filename, "w") 82 | pyoutfile.write(rendered) 83 | pyoutfile.close() 84 | self.casecount = self.casecount 85 | 86 | -------------------------------------------------------------------------------- /generator/lib/awsapigen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code to generate the Leonidas API 3 | """ 4 | 5 | import os 6 | 7 | 8 | class AWSAPIGen: 9 | """ 10 | Generates the Flask API used to execute the attacker actions 11 | """ 12 | 13 | def __init__(self, config, env): 14 | self.templates = {} 15 | self.env = env 16 | self.config = config 17 | self.templates["aws_python_function"] = env.get_template( 18 | "aws_python_execution_function.jinja2" 19 | ) 20 | self.templates["api_core"] = env.get_template("python_api_core.jinja2") 21 | self.templates["serverless_config"] = env.get_template("serverless.jinja2") 22 | self.import_list = [] 23 | self.casecount = 0 24 | 25 | def generate_python_api(self, outdir, definitions): 26 | """ 27 | Generate the flask API that is deployed as a lambda function 28 | """ 29 | for category in definitions.categories: 30 | self.import_list.append(category.replace(" ", "_").lower()) 31 | self._generate_api_category(outdir, category, definitions) 32 | 33 | # API root file 34 | rendered = self.templates["api_core"].render({"categories": self.import_list}) 35 | filename = os.path.join(outdir, "leonidas.py") 36 | with open(filename, "w") as apioutfile: 37 | apioutfile.write(rendered) 38 | 39 | # Serverless config 40 | filename = os.path.join(outdir, "serverless.yml") 41 | rendered = self.get_serverless_config(definitions) 42 | with open(filename, "w") as serverlessconfiguoutfile: 43 | serverlessconfiguoutfile.write(rendered) 44 | 45 | def _generate_api_category(self, outdir, category, definitions): 46 | """ 47 | Build a given category of test cases 48 | """ 49 | outdir = os.path.join(outdir, "api") 50 | if not os.path.exists(outdir): 51 | os.makedirs(outdir) 52 | category_case_set = [] 53 | for case in definitions.case_set: 54 | if case["category"] == category: 55 | try: 56 | if case["executors"]["leonidas_aws"]["implemented"]: 57 | category_case_set.append(case) 58 | self.casecount = self.casecount + 1 59 | except KeyError: 60 | continue 61 | rendered = self.templates["aws_python_function"].render( 62 | { 63 | "cases": category_case_set, 64 | "category": category.replace(" ", "_").lower(), 65 | } 66 | ) 67 | py_filename = category.replace(" ", "_").lower() + ".py" 68 | filename = os.path.join(outdir, py_filename) 69 | pyoutfile = open(filename, "w") 70 | pyoutfile.write(rendered) 71 | pyoutfile.close() 72 | self.casecount = self.casecount 73 | 74 | def get_serverless_config(self, definitions): 75 | """ 76 | Render the serverless.yml that Serverless uses as a configuration file 77 | """ 78 | try: 79 | rendered = self.templates["serverless_config"].render( 80 | { 81 | "permissions": sorted(list(definitions.permissions["aws"])), 82 | "region": self.config["region"], 83 | "resources": self.config["resources"], 84 | } 85 | ) 86 | except KeyError: 87 | rendered = self.templates["serverless_config"].render( 88 | { 89 | "permissions": sorted(list(definitions.permissions["aws"])), 90 | "region": self.config["region"], 91 | "resources": ['"*"'], 92 | } 93 | ) 94 | return rendered 95 | -------------------------------------------------------------------------------- /jupyter/threat-actors/demo-envs/dharma.yml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: dharma-prod 5 | --- 6 | kind: CertificateSigningRequest 7 | apiVersion: certificates.k8s.io/v1 8 | metadata: 9 | name: jlocke-csr 10 | namespace: dharma-prod 11 | spec: 12 | groups: 13 | - system:authenticated 14 | signerName: kubernetes.io/kube-apiserver-client 15 | request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dhbXh2WTJ0bE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQS9QcW1PQTRaS1hqUnNESWpQOWZmK2JtcnhEOFpJK1pKdDkwRk5TcDg5R0F3CnRXRVBvanFkazVFWGFpaWsxcWRnSVpTUXlxR2NscThSd2VjY2pwOTNTQXJLajBLNHF3UlZ3aDNGT3VUWWlEV1UKckVkekZMcmJheUQ1dlJ3OXlGbHJBMXNETmVPS3cyY0lPUHBacGUwbHdOdGxjcTVXNlZGMlZhamNVUEJpUG1yZwpkU2NLeGlXQ3Y5L3dwOVRWOVJvR0N4WFlPWEZlN1NlaHhiWGgwcC92cDlIUmo3UDB4UWpqL0hCYUZZc004dDBICkRXZlUvZDErcVQrOEFXa1hIbjRXdUVESDBUU1YvbmZqSENOaWhuQi9yb2ltUFhrd1lXSHduSG9qeHZWNkdhRVYKeHRHSEtGdzBFdmRSRXB6UGJ6MjRBS1VpYXpySjkrakhrV3M1dHNOVW1RSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBQ1J6azVNci9RVjczUFFqekVoREtIbU5lODZiRVNLM3JCd0RNZzhMdzZ0SE13QzNqT2w1ClFPRVp0WVZEU1pOM2NLWlNPTFd6bzJsVzIxT3U0bmNHSWVHQ05sVnFmazhPaU53S09YZit5bFRXdktzV2t6K2YKaUo1TTBSNFdnbm1lS2p2UnlGOUI3UndBWThHVGtRNjlOd1ZnTFVIMjZBUzcyUnVCK25Ka2xrVkJ1N0d1UHhEQQpPWnpXYzlvMy8wZ1dTUjRWM0d4RFFrdHpORCtrNXB5VXBMdjVQVnhrOXllMFY5cmRnUjRNQ3NuWXdPYW5kaFpaCnJuMEVNaFI0UFA0cHRKcko3cy9ETUZLaXllNDhCVVBBZTNqa3dVRFRpTWEzdVVDUVQzcHQrR1RTZnJZSzBndVgKWjlvRWtBQk5GYjhCdmUxNTBNR1MwVnh1d3FUdU0rdUJyVlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo= 16 | usages: 17 | - digital signature 18 | - key encipherment 19 | - client auth 20 | --- 21 | kind: ServiceAccount 22 | apiVersion: v1 23 | metadata: 24 | name: lamppost-sa 25 | namespace: dharma-prod 26 | --- 27 | apiVersion: v1 28 | kind: Secret 29 | metadata: 30 | namespace: dharma-prod 31 | name: lamppost-sa-secret 32 | annotations: 33 | kubernetes.io/service-account.name: lamppost-sa 34 | type: kubernetes.io/service-account-token 35 | --- 36 | kind: Role 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | metadata: 39 | namespace: dharma-prod 40 | name: jlocke-role 41 | rules: 42 | - apiGroups: [""] 43 | resources: ["pods","pods/exec", "secrets"] 44 | verbs: ["get", "list", "create", "update", "delete", "exec", "watch", "patch", "edit"] 45 | --- 46 | kind: RoleBinding 47 | apiVersion: rbac.authorization.k8s.io/v1 48 | metadata: 49 | namespace: dharma-prod 50 | name: lamppost-rb 51 | subjects: 52 | - kind: ServiceAccount 53 | name: lamppost-sa 54 | apiGroup: "" 55 | roleRef: 56 | kind: Role 57 | name: jlocke-role 58 | apiGroup: "" 59 | --- 60 | kind: RoleBinding 61 | apiVersion: rbac.authorization.k8s.io/v1 62 | metadata: 63 | namespace: dharma-prod 64 | name: jlocke-rb 65 | subjects: 66 | - kind: User 67 | name: jlocke 68 | apiGroup: "" 69 | roleRef: 70 | kind: Role 71 | name: jlocke-role 72 | apiGroup: "" 73 | --- 74 | kind: Secret 75 | apiVersion: v1 76 | metadata: 77 | name: patient-db-creds 78 | namespace: dharma-prod 79 | data: 80 | root-password: VkFMM05aMzc3MQ== 81 | --- 82 | kind: Pod 83 | apiVersion: v1 84 | metadata: 85 | namespace: dharma-prod 86 | name: patient-db 87 | spec: 88 | containers: 89 | - image: mysql:5.6 90 | name: mysql 91 | env: 92 | - name: MYSQL_ROOT_PASSWORD 93 | valueFrom: 94 | secretKeyRef: 95 | name: patient-db-creds 96 | key: root-password 97 | --- 98 | kind: Pod 99 | apiVersion: v1 100 | metadata: 101 | namespace: dharma-prod 102 | name: hvac-controller 103 | spec: 104 | containers: 105 | - image: library/gcc 106 | name: libc 107 | command: ["/bin/sh", "-c", "sleep infinity"] 108 | --- 109 | -------------------------------------------------------------------------------- /aws-pipeline/codecommit.tf: -------------------------------------------------------------------------------- 1 | resource "aws_codecommit_repository" "repo" { 2 | repository_name = "Leonidas" 3 | description = "CodeBuild Repository containing attacker actions and all the infrastructure" 4 | } 5 | 6 | resource "aws_iam_user" "codecommit_user" { 7 | name = "codecommituser-${lower(terraform.workspace)}" 8 | path = "/${lower(terraform.workspace)}/" 9 | force_destroy = true 10 | } 11 | 12 | resource "aws_iam_user_ssh_key" "codecommit_user_key" { 13 | username = "${aws_iam_user.codecommit_user.name}" 14 | encoding = "SSH" 15 | public_key = "${file(var.codecommit_ssh_key)}" 16 | status = "Active" 17 | } 18 | 19 | resource "aws_iam_group" "codecommit_group" { 20 | name = "codecommit-${lower(terraform.workspace)}" 21 | path = "/${lower(terraform.workspace)}/" 22 | } 23 | 24 | resource "aws_iam_group_membership" "codecommit_group_membership" { 25 | name = "codecommit-group-membership-${lower(terraform.workspace)}" 26 | 27 | users = [ 28 | "${aws_iam_user.codecommit_user.name}", 29 | ] 30 | 31 | group = "${aws_iam_group.codecommit_group.name}" 32 | } 33 | 34 | resource "aws_iam_policy" "codecommit_policy" { 35 | name = "codecommit-policy-${lower(terraform.workspace)}" 36 | path = "/${lower(terraform.workspace)}/" 37 | description = "CodeCommit policy for ${lower(terraform.workspace)}" 38 | 39 | policy = < serverless.yml` 59 | * `sls remove` 60 | * Change directory into `aws-pipeline/` 61 | * `terraform destroy` 62 | * Check both the intended deployment region and us-east-1 for lingering S3 buckets, as the AWS API will not allow deletion of non-empty buckets. This can instead be done through the console. 63 | 64 | ## Deploying Leonidas in a Kubernetes Cluster 65 | 66 | Leonidas can be deployed as a containerised application within a pod in an existing Kubernetes cluster. On a high level, this includes generating the Python API from the definitions, packaging the code into a container image and pushing it to a repository, which can then be pulled into a Kubernetes pod as part of the Leonidas Kubernetes resources deployed within the cluster. All these steps can be carried out using the **generator** utility. 67 | 68 | It is recommended that **ephemeral images** are used for the Leonidas image. An example public repository supporting ephemeral images is [ttl.sh](ttl.sh), which is used in the commands below. 69 | 70 | ### Building and Deploying the API 71 | 72 | The complete list of commands needed to deploy the Leonidas API within a cluster are provided below: 73 | 0. Modify `config.yml` to specify 74 | - `image_url`: the image URL i.e. the registry to temporary push the Leonidas container image so that the cluster can pull it from. We recommend the use of *ephemeral images*, therefore the current default value uses the [ttl.sh](ttl.sh) registry with a few minutes image lifespan 75 | - `namespace`: the namespace which Leonidas resources should be deployed in. If not specified, Leonidas will be deployed into the `default` namespace. 76 | 1. `cd generator/` - Navigate to the generator directory 77 | 2. `poetry install` - Install generator dependencies 78 | 3. `poetry run ./generator.py generate-kube-api` - To generate the Python API code from the YAML definitions 79 | 4. `poetry run ./generator.py build-image` - Build the Leonidas container image and push it to the remote registry. This might take a while. 80 | 5. `poetry run ./generator.py kube-resources > leonidas-manifests.yml` - Create the manifest of Kubernetes resources Leonidas needs, including the RBAC policy 81 | - by default, this command assumes cluster-wide operation and generates a cluster role. To specify explicitly whether Leonidas should be granted namespaced or cluster-wide permissions, set the `--role/--clusterrole` flag 82 | 6. `kubectl -f leonidas-manifests.yml apply` - Create the Leonidas resources in the cluster. This might take a minute. 83 | 7. `kubectl port-forward deployment/leonidas-deployment 5000:5000` - Expose the Flask web service on http://localhost:5000 so that it can be accessed by clients such as `leo`, Jupyter notebooks, or just `curl`. Note that if you chose to deploy Leonidas into a namespace other than the default, you will need to also provide the `-n ` flag here 84 | 85 | ### Removing Leonidas from a cluster 86 | To clean up all Leonidas-related resources after use, simply run 87 | `kubectl -f leonidas-manifests.yml delete` 88 | 89 | ### Generating an RBAC Policy for the API 90 | 91 | The generator utility allows generation of only the RBAC policy needed by Leonidas, without the other Kubernetes resources. This can be done using the `rbac-policy` command. 92 | 93 | Leonidas can be configured to run with cluster-wide permissions, or limited within a specific namespace. In practice, this means that the service account the application is running as can be assigned either a `ClusterRole`, or a namespace-scoped `Role`. In the latter scenario, cluster-wide permissions specified by test cases are ignored. See [Writing Definitions](writing-definitions.md) on how this is specified. Depending on the mode of operation desired, the RBAC policy is generated as follows: 94 | 95 | ```bash 96 | # Cluster-wide operation 97 | $ poetry run ./generator.py rbac-policy > ../policy.yml 98 | Generating ClusterRole with 20 permissions 99 | 100 | # Namespace-scoped operation 101 | # set the target namespace in generator/config.yml 102 | $ poetry run ./generator.py rbac-policy --role > ../policy.yml 103 | Generating Role with 16 permissions (ignored 4 clusterwide permissions) 104 | ``` 105 | 106 | ## Running the Leonidas API Locally 107 | 108 | It is possible to build and run the Leonidas Python API locally for development purposes. To do this: 109 | 110 | * `cd generator` 111 | * `poetry install` 112 | * `poetry run ./generator.py generate-aws-api` and/or `generate-kube-api` 113 | * `cd ../output/leonidas` 114 | * `poetry install` 115 | * `poetry run python leonidas.py` 116 | 117 | This will spawn the API listening at http://127.0.0.1:5000. By default, this uses whichever AWS credentials are configured as the default profile in `~/.aws/config`, and whichever Kubernetes credentials found in the default Kubeconfig. These can be overridden by supplying a role ARN to assume, access keys, JWT tokens or TLS client certificates to use for requests to the API. 118 | 119 | 120 | -------------------------------------------------------------------------------- /generator/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import os 5 | from pathlib import Path 6 | import shlex 7 | 8 | import jinja2 9 | import jinja2_ansible_filters 10 | import typer 11 | import yaml 12 | import docker 13 | 14 | from lib.definitions import Definitions 15 | from lib.awsapigen import AWSAPIGen 16 | from lib.kubeapigen import KubeAPIGen 17 | from lib.docgen import DocGen 18 | from lib.leo_case_gen import LeoCaseGen 19 | from lib.sigmaexport import SigmaExport 20 | 21 | 22 | def debug(text): 23 | print(text) 24 | return "" 25 | 26 | def escape_shell(input): 27 | """Custom filter used by the Jinja template generating the Python code from YAML definitions. 28 | Escapes the "code" field specified by the test case author, before passing it to subprocess.run()""" 29 | return shlex.quote(input) 30 | 31 | env = jinja2.Environment( 32 | loader=jinja2.FileSystemLoader(os.path.join(".", "templates")), 33 | autoescape=jinja2.select_autoescape(["html", "xml"]), 34 | extensions=['jinja2_ansible_filters.AnsibleCoreFiltersExtension'] # used for the to_nice_yaml filter in docs template 35 | ) 36 | 37 | env.filters["debug"] = debug 38 | env.filters['escape_shell'] = escape_shell 39 | 40 | 41 | class Generator: 42 | def initialise(self, config, env): 43 | self.config = config 44 | self.definitions = Definitions(self.config, env) 45 | self.docgen = DocGen(self.config, env) 46 | self.awsapigen = AWSAPIGen(self.config, env) 47 | self.kubeapigen = KubeAPIGen(self.config, env) 48 | self.leocasegen = LeoCaseGen(self.config, env) 49 | self.sigmaexport = SigmaExport(self.config, env) 50 | 51 | 52 | app = typer.Typer() 53 | generator = Generator() 54 | 55 | 56 | @app.callback() 57 | def main( 58 | config: Path = typer.Option( 59 | "config.yml", help="Path to config file", show_default=True 60 | ) 61 | ): 62 | config = yaml.safe_load(open(config, "r").read()) 63 | if not os.path.exists(config["output_dir"]): 64 | os.makedirs(config["output_dir"]) 65 | generator.initialise(config, env) 66 | 67 | 68 | @app.command() 69 | def definitions(): 70 | """ 71 | Pretty-print definitions dictionary 72 | """ 73 | generator.definitions.construct_definitions() 74 | print(json.dumps(generator.definitions.case_set, indent=4)) 75 | 76 | 77 | @app.command() 78 | def validate(): 79 | """ 80 | Validate the test definitions 81 | """ 82 | if not generator.definitions.validate(): 83 | print("Validation failed") 84 | raise typer.Exit(code=1) 85 | else: 86 | print( 87 | "Validation successful - validated {} cases".format( 88 | generator.definitions.casecount 89 | ) 90 | ) 91 | 92 | 93 | @app.command() 94 | def generate_aws_api(): 95 | """ 96 | Generate the AWS Leonidas API 97 | """ 98 | print("Generating API") 99 | generator.definitions.construct_definitions() 100 | PYOUTPUTDIR = "leonidas" 101 | outdir = os.path.join(generator.config["output_dir"], PYOUTPUTDIR) 102 | generator.awsapigen.generate_python_api(outdir, generator.definitions) 103 | print( 104 | "API generation complete - {} cases generated".format( 105 | generator.awsapigen.casecount 106 | ) 107 | ) 108 | 109 | # TODO (see #6) currently code is duplicated in kubeapigen / awsapigen to avoid breaking things, 110 | # Split awsapigen to become independent of AWS stuff (building serverless.yml) and use it in both locations 111 | @app.command() 112 | def generate_kube_api(): 113 | """ 114 | Generate the Kubernetes Leonidas API 115 | """ 116 | print("Generating API locally") 117 | generator.definitions.construct_definitions() 118 | PYOUTPUTDIR = "leonidas" 119 | outdir = os.path.join(generator.config["output_dir"], PYOUTPUTDIR) 120 | generator.kubeapigen.generate_python_api(outdir, generator.definitions) 121 | print( 122 | "API generation complete - {} cases generated".format( 123 | generator.kubeapigen.casecount 124 | ) 125 | ) 126 | 127 | @app.command() 128 | def build_image( 129 | verbose: bool = typer.Option(False, help="Show runtime output from docker commands."), 130 | ): 131 | """ 132 | Build and push the Leonidas container image. Specify the registry in config.yml 133 | """ 134 | try: 135 | client = docker.from_env() 136 | except docker.errors.DockerException: 137 | print("Docker deamon could not be reached!") 138 | raise typer.Exit(code=1) 139 | 140 | 141 | print("Building image") 142 | for item in client.images.build( 143 | tag=generator.config["image_url"], 144 | path=generator.config["output_dir"], 145 | # buildargs={"OUTPUT_DIR":generator.config["output_dir"]} 146 | )[1]: 147 | if verbose: 148 | for key, value in item.items(): 149 | if key == 'stream': 150 | text = value.strip() 151 | if text: 152 | print(text, flush=True) 153 | 154 | print("Pushing image") 155 | for item in client.images.push( 156 | generator.config["image_url"], 157 | stream=True, 158 | decode=True 159 | ): 160 | if verbose: 161 | for key, value in item.items(): 162 | if key == 'status': 163 | print(value, flush=True) 164 | 165 | 166 | @app.command() 167 | def docs(): 168 | """ 169 | Generate the leonidas documentation (http://detectioninthe.cloud) 170 | """ 171 | print("Generating Docs") 172 | generator.definitions.construct_definitions() 173 | MARKDOWNOUTPUTDIR = "docs" 174 | outdir = os.path.join(generator.config["output_dir"], MARKDOWNOUTPUTDIR) 175 | generator.docgen.generate_markdown(outdir, generator.definitions) 176 | 177 | 178 | @app.command() 179 | def iam_policy(): 180 | """ 181 | Print the AWS IAM policy necessary to execute all the AWS test cases 182 | """ 183 | generator.definitions.construct_definitions() 184 | print(generator.definitions.get_aws_policy()) 185 | 186 | @app.command() 187 | def kube_resources( 188 | role: bool = typer.Option(False, "--role/--clusterrole", help="Assign Leonidas a namespaced role or a ClusterRole") 189 | ): 190 | """ 191 | Print the Kubernetes YAML resources 192 | """ 193 | generator.definitions.construct_definitions() 194 | print(generator.kubeapigen.generate_kube_resources(role)) # TODO rename KubeAPIGen to KubeGen 195 | print(generator.definitions.get_rbac_policy(role)) 196 | 197 | @app.command() 198 | def rbac_policy( 199 | role: bool = typer.Option(False, "--role/--clusterrole", help="Assign Leonidas a namespaced role or a ClusterRole") 200 | ): 201 | """ 202 | Print the Kubernetes RBAC policy as YAML, including all the permissions necessary to execute the Kubernetes test cases 203 | """ 204 | generator.definitions.construct_definitions() 205 | print(generator.definitions.get_rbac_policy(role)) 206 | 207 | 208 | 209 | @app.command() 210 | def serverless_config(): 211 | """ 212 | Print the Serverless framework configuration 213 | used to deploy the Leonidas API to AWS 214 | """ 215 | generator.definitions.construct_definitions() 216 | print(generator.awsapigen.get_serverless_config(generator.definitions)) 217 | 218 | 219 | @app.command() 220 | def leo(): 221 | """ 222 | Generate the configuration file for Leo, the CLI tool 223 | for executing test cases against an instance of Leonidas 224 | """ 225 | print("Generating Leo Case Configuration") 226 | generator.definitions.construct_definitions() 227 | LEOCASEOUTPUTDIR = "caseconfig" 228 | outdir = os.path.join(generator.config["output_dir"], LEOCASEOUTPUTDIR) 229 | generator.leocasegen.generate_leo_cases(generator.definitions, outdir) 230 | 231 | 232 | @app.command() 233 | def sigma(): 234 | """ 235 | Generate the Sigma rule definitions 236 | """ 237 | print("Generating Sigma rules") 238 | generator.definitions.construct_definitions() 239 | SIGMAOUTPUTDIR = "sigma" 240 | outdir = os.path.join(generator.config["output_dir"], SIGMAOUTPUTDIR) 241 | generator.sigmaexport.export_sigma(generator.definitions, outdir) 242 | 243 | 244 | if __name__ == "__main__": 245 | app() 246 | --------------------------------------------------------------------------------