This commit is contained in:
Carlos Polop
2026-05-26 17:55:54 +02:00
parent 8cb43f6b52
commit 06a089227c
34 changed files with 135 additions and 111 deletions
+71 -46
View File
@@ -38,6 +38,16 @@ SITE_DESCRIPTIONS = {
"Kubernetes, CI/CD, and workspace pentesting techniques."
),
}
SITE_ALTERNATE_NAMES = {
"HackTricks Cloud": ["HT Cloud", "HackTricks Wiki Cloud"],
}
SITE_SAME_AS = {
"HackTricks Cloud": [
"https://github.com/HackTricks-wiki/hacktricks-cloud",
"https://www.linkedin.com/company/hacktricks",
"https://twitter.com/hacktricks_live",
],
}
LANGUAGE_LOCALES = {
"af": "af_ZA",
"de": "de_DE",
@@ -163,10 +173,6 @@ def homepage_description(site_name):
return SITE_DESCRIPTIONS.get(site_name, f"{site_name}: practical cloud security guides and references.")
def strip_index_suffix(path):
return re.sub(r"(?:^|/)index\.html$", "", path.as_posix())
def is_homepage(rel_path):
return rel_path.as_posix() == "index.html"
@@ -211,40 +217,62 @@ def humanize_slug(value):
def breadcrumb_items(site_url, lang, rel_path):
items = [{"name": "Home", "url": canonical_url(site_url, lang, Path("index.html"))}]
bare_path = strip_index_suffix(rel_path)
if not bare_path:
if is_homepage(rel_path):
return items
parts = [part for part in bare_path.split("/") if part]
for idx in range(len(parts)):
crumb_rel = Path(*parts[: idx + 1], "index.html")
items.append({"name": humanize_slug(parts[idx]), "url": canonical_url(site_url, lang, crumb_rel)})
if rel_path.name == "index.html":
directory_parts = list(rel_path.parent.parts)
page_name = None
else:
directory_parts = [] if rel_path.parent == Path(".") else list(rel_path.parent.parts)
page_name = rel_path.name
for idx, part in enumerate(directory_parts):
crumb_rel = Path(*directory_parts[: idx + 1], "index.html")
items.append({"name": humanize_slug(part), "url": canonical_url(site_url, lang, crumb_rel)})
if page_name:
items.append({"name": humanize_slug(page_name), "url": canonical_url(site_url, lang, rel_path)})
return items
def build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages):
def build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages, lastmod):
current_url = canonical_url(site_url, lang, rel_path)
site_root = site_url.rstrip("/")
website_url = canonical_url(site_url, "en", Path("index.html"))
data = [
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": f"{site_root}/#organization",
"name": site_name,
"url": site_root,
"logo": {"@type": "ImageObject", "url": image_url},
},
{
"@context": "https://schema.org",
"@type": "WebSite",
"@id": f"{site_root}/#website",
"url": site_root,
"name": site_name,
"inLanguage": languages,
"publisher": {"@id": f"{site_root}/#organization"},
},
{
data = []
if is_homepage(rel_path):
data.extend(
[
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": f"{site_root}/#organization",
"name": site_name,
"alternateName": SITE_ALTERNATE_NAMES.get(site_name, []),
"url": site_root,
"description": homepage_description(site_name),
"logo": {"@type": "ImageObject", "url": image_url},
"sameAs": SITE_SAME_AS.get(site_name, []),
},
{
"@context": "https://schema.org",
"@type": "WebSite",
"@id": f"{site_root}/#website",
"url": site_root,
"name": site_name,
"alternateName": SITE_ALTERNATE_NAMES.get(site_name, []),
"description": homepage_description(site_name),
"inLanguage": languages,
"publisher": {"@id": f"{site_root}/#organization"},
},
]
)
data.extend(
[
{
"@context": "https://schema.org",
"@type": "WebPage",
"@id": f"{current_url}#webpage",
@@ -252,11 +280,12 @@ def build_structured_data(site_url, lang, rel_path, title, description, site_nam
"name": title,
"description": description,
"inLanguage": lang,
"dateModified": lastmod,
"isPartOf": {"@id": f"{site_root}/#website"},
"about": {"@id": f"{site_root}/#organization"},
"primaryImageOfPage": {"@type": "ImageObject", "url": image_url},
},
{
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
@@ -268,24 +297,18 @@ def build_structured_data(site_url, lang, rel_path, title, description, site_nam
}
for index, item in enumerate(breadcrumb_items(site_url, lang, rel_path), start=1)
],
},
]
if is_homepage(rel_path):
data[1]["potentialAction"] = {
"@type": "SearchAction",
"target": f"{website_url}?search={{search_term_string}}",
"query-input": "required name=search_term_string",
}
},
]
)
return data
def build_seo_block(site_url, lang, rel_path, languages, default_lang, title, description, site_name):
def build_seo_block(site_url, lang, rel_path, languages, default_lang, title, description, site_name, lastmod):
current_url = canonical_url(site_url, lang, rel_path)
image_url = social_image_url(site_url)
structured_data = json.dumps(
build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages),
build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages, lastmod),
ensure_ascii=False,
separators=(",", ":"),
)
@@ -335,13 +358,13 @@ def update_language_menu_links(document, rel_path, languages):
return LANGUAGE_MENU_LINK_RE.sub(replace, document)
def update_document(document, site_url, lang, rel_path, languages, default_lang, site_name):
def update_document(document, site_url, lang, rel_path, languages, default_lang, site_name, lastmod):
title_match = re.search(r"<title>(.*?)</title>", document, flags=re.I | re.S)
page_title = clean_text(title_match.group(1)) if title_match else site_name
fallback_description = f"{site_name}: {page_title}"
description = homepage_description(site_name) if is_homepage(rel_path) else extract_description(document, fallback_description)
seo_block = build_seo_block(
site_url, lang, rel_path, languages, default_lang, page_title, description, site_name
site_url, lang, rel_path, languages, default_lang, page_title, description, site_name, lastmod
)
document = re.sub(
@@ -457,6 +480,7 @@ def process_pages(args):
for html_file in iter_html_files(book_dir):
rel_path = html_file.relative_to(book_dir)
content = html_file.read_text(encoding="utf-8")
lastmod = page_lastmod(book_dir, rel_path, html_file)
updated = update_document(
content,
args.site_url,
@@ -465,6 +489,7 @@ def process_pages(args):
languages,
args.default_lang,
args.site_name,
lastmod,
)
html_file.write_text(updated, encoding="utf-8")
@@ -62,13 +62,13 @@ If you have **access to the web console** you might be able to access some or al
Variables can be stored in Airflow so the **DAGs** can **access** their values. It's similar to secrets of other platforms. If you have **enough permissions** you can access them in the GUI in `http://<airflow>/variable/list/`.\
Airflow by default will show the value of the variable in the GUI, however, according to [**this**](https://marclamberti.com/blog/variables-with-apache-airflow/) it's possible to set a **list of variables** whose **value** will appear as **asterisks** in the **GUI**.
![](<../../images/image (164).png>)
![Airflow Variables page showing AWS_ACCESS_KEY_ID and a masked AWS_SECRET_ACCESS_KEY](<../../images/image (164).png>)
However, these **values** can still be **retrieved** via **CLI** (you need to have DB access), **arbitrary DAG** execution, **API** accessing the variables endpoint (the API needs to be activated), and **even the GUI itself!**\
To access those values from the GUI just **select the variables** you want to access and **click on Actions -> Export**.\
Another way is to perform a **bruteforce** to the **hidden value** using the **search filtering** it until you get it:
![](<../../images/image (152).png>)
![Airflow Variables search filter returning the AWS_SECRET_ACCESS_KEY variable](<../../images/image (152).png>)
#### Privilege Escalation
+2 -2
View File
@@ -6,7 +6,7 @@
Atlantis basically helps you to to run terraform from Pull Requests from your git server.
![](<../images/image (161).png>)
![Atlantis pull request workflow showing plan and apply comments in the PR lifecycle](<../images/image (161).png>)
### Local Lab
@@ -304,7 +304,7 @@ Moreover, if you don't have configured in the **branch protection** to ask to **
This is the **setting** in Github branch protections:
![](<../images/image (216).png>)
![GitHub branch protection option to dismiss stale pull request approvals after new commits](<../images/image (216).png>)
#### Webhook Secret
+2 -2
View File
@@ -70,7 +70,7 @@ jobs:
These are **secrets** that are only going to be **accessible** by the **project** (by **any branch**).\
You can see them **declared in** _https://app.circleci.com/settings/project/github/\<org_name>/\<repo_name>/environment-variables_
![](<../images/image (129).png>)
![CircleCI project environment variables page with MY_ENV_VAR and a masked secret value](<../images/image (129).png>)
> [!CAUTION]
> The "**Import Variables**" functionality allows to **import variables from other projects** to this one.
@@ -79,7 +79,7 @@ You can see them **declared in** _https://app.circleci.com/settings/project/gith
These are secrets that are **org wide**. By **default any repo** is going to be able to **access any secret** stored here:
![](<../images/image (123).png>)
![CircleCI context security group page showing All members allowed to execute the context](<../images/image (123).png>)
> [!TIP]
> However, note that a different group (instead of All members) can be **selected to only give access to the secrets to specific people**.\
@@ -10,7 +10,7 @@
### Architecture
![](<../../images/image (187).png>)
![Concourse architecture diagram with load balancer, ATC, TSA, Beacon, Garden, and Baggageclaim components](<../../images/image (187).png>)
#### ATC: web UI & build scheduler
@@ -6,7 +6,7 @@
**Gitea** is a **self-hosted community managed lightweight code hosting** solution written in Go.
![](<../../images/image (160).png>)
![Gitea repository page showing files, branches, commits, tags, and repository statistics](<../../images/image (160).png>)
### Basic Information
@@ -34,7 +34,7 @@ When creating a new team, several important settings are selected:
- **Administrator** access
- **Specific** access:
![](<../../images/image (118).png>)
![Gitea organization repository permission matrix for owner, contributor, reader, and access roles](<../../images/image (118).png>)
### Teams & Users
@@ -66,7 +66,7 @@ You can generate personal access token to **give an application access to your a
Just like personal access tokens **Oauth applications** will have **complete access** over your account and the places your account has access because, as indicated in the [docs](https://docs.gitea.io/en-us/oauth2-provider/#scopes), scopes aren't supported yet:
![](<../../images/image (194).png>)
![Gitea OAuth authorization prompt for TestApp requesting full account and organization access](<../../images/image (194).png>)
### Deploy keys
@@ -28,7 +28,7 @@ Without credentials you can look inside _**/asynchPeople/**_ path or _**/securit
You may be able to get the Jenkins version from the path _**/oops**_ or _**/error**_
![](<../../images/image (146).png>)
![Jenkins Oops error page exposing the Jenkins version in the footer](<../../images/image (146).png>)
### Known Vulnerabilities
@@ -121,7 +121,7 @@ If the plugin reuses stored creds, Jenkins will attempt to authenticate to `atta
If the compromised user has **enough privileges to create/modify a new Jenkins node** and SSH credentials are already stored to access other nodes, he could **steal those credentials** by creating/modifying a node and **setting a host that will record the credentials** without verifying the host key:
![](<../../images/image (218).png>)
![Jenkins node configuration form with host, credentials, and non-verifying host key strategy fields](<../../images/image (218).png>)
You will usually find Jenkins ssh credentials in a **global provider** (`/credentials/`), so you can also dump them as you would dump any other secret. More information in the [**Dumping secrets section**](#dumping-secrets).
@@ -163,7 +163,7 @@ To exploit pipelines you still need to have access to Jenkins.
**Pipelines** can also be used as **build mechanism in projects**, in that case it can be configured a **file inside the repository** that will contains the pipeline syntax. By default `/Jenkinsfile` is used:
![](<../../images/image (127).png>)
![Jenkins pipeline build configuration using Jenkinsfile mode and script path](<../../images/image (127).png>)
It's also possible to **store pipeline configuration files in other places** (in other repositories for example) with the goal of **separating** the repository **access** and the pipeline access.
@@ -278,7 +278,7 @@ basic-jenkins-information.md
You can enumerate the **configured nodes** in `/computer/`, you will usually find the \*\*`Built-In Node` \*\* (which is the node running Jenkins) and potentially more:
![](<../../images/image (249).png>)
![Jenkins node list showing agent1 and Built-In Node executors](<../../images/image (249).png>)
It is **specially interesting to compromise the Built-In node** because it contains sensitive Jenkins information.
@@ -358,7 +358,7 @@ You can list the secrets accessing `/credentials/` if you have enough permission
If you can **see the configuration of each project**, you can also see in there the **names of the credentials (secrets)** being use to access the repository and **other credentials of the project**.
![](<../../images/image (180).png>)
![Jenkins credentials selector showing gitea-access-token and Add credential button](<../../images/image (180).png>)
#### From Groovy
@@ -33,7 +33,7 @@ In `/configureSecurity` it's possible to **configure the authorization method of
- **Logged-in users can do anything**: In this mode, every **logged-in user gets full control** of Jenkins. The only user who won't have full control is **anonymous user**, who only gets **read access**.
- **Matrix-based security**: You can configure **who can do what** in a table. Each **column** represents a **permission**. Each **row** **represents** a **user or a group/role.** This includes a special user '**anonymous**', which represents **unauthenticated users**, as well as '**authenticated**', which represents **all authenticated users**.
![](<../../images/image (149).png>)
![Jenkins matrix-based authorization table with permissions columns for users and groups](<../../images/image (149).png>)
- **Project-based Matrix Authorization Strategy:** This mode is an **extension** to "**Matrix-based security**" that allows additional ACL matrix to be **defined for each project separately.**
- **Role-Based Strategy:** Enables defining authorizations using a **role-based strategy**. Manage the roles in `/role-strategy`.
@@ -6,11 +6,11 @@
In "New Item" (accessible in `/view/all/newJob`) select **Pipeline:**
![](<../../images/image (235).png>)
![Jenkins New Item page with Pipeline selected as the project type](<../../images/image (235).png>)
In the **Pipeline section** write the **reverse shell**:
![](<../../images/image (285).png>)
![Jenkins Pipeline script editor containing a Groovy reverse shell payload](<../../images/image (285).png>)
```groovy
pipeline {
@@ -30,7 +30,7 @@ pipeline {
Finally click on **Save**, and **Build Now** and the pipeline will be executed:
![](<../../images/image (228).png>)
![Jenkins build console showing a reverse shell connection and whoami output](<../../images/image (228).png>)
## Modifying a Pipeline
@@ -12,13 +12,13 @@ This method is very noisy because you have to create a hole new project (obvious
1. If **Build now** button doesn't appear, you can still go to **configure** --> **Build Triggers** --> `Build periodically` and set a cron of `* * * * *`
2. Instead of using cron, you can use the config "**Trigger builds remotely**" where you just need to set a the api token name to trigger the job. Then go to your user profile and **generate an API token** (call this API token as you called the api token to trigger the job). Finally, trigger the job with: **`curl <username>:<api_token>@<jenkins_url>/job/<job_name>/build?token=<api_token_name>`**
![](<../../images/image (165).png>)
![Jenkins New Item page for creating a Freestyle project](<../../images/image (165).png>)
## Modifying a Project
Go to the projects and check **if you can configure any** of them (look for the "Configure button"):
![](<../../images/image (265).png>)
![Jenkins project side menu with the Configure action visible](<../../images/image (265).png>)
If you **cannot** see any **configuration** **button** then you **cannot** **configure** it probably (but check all projects as you might be able to configure some of them and not others).
@@ -28,7 +28,7 @@ Or **try to access to the path** `/job/<proj-name>/configure` or `/me/my-views/v
If you are allowed to configure the project you can **make it execute commands when a build is successful**:
![](<../../images/image (98).png>)
![Jenkins build step text area containing a reverse shell command](<../../images/image (98).png>)
Click on **Save** and **build** the project and your **command will be executed**.\
If you are not executing a reverse shell but a simple command you can **see the output of the command inside the output of the build**.
+2 -2
View File
@@ -12,7 +12,7 @@ HashiCorp Terraform is an **infrastructure as code tool** that lets you define b
Terraform creates and manages resources on cloud platforms and other services through their application programming interfaces (APIs). Providers enable Terraform to work with virtually any platform or service with an accessible API.
![](<../images/image (177).png>)
![Terraform provider workflow diagram connecting Terraform to a provider and target API](<../images/image (177).png>)
HashiCorp and the Terraform community have already written **more than 1700 providers** to manage thousands of different types of resources and services, and this number continues to grow. You can find all publicly available providers on the [Terraform Registry](https://registry.terraform.io/), including Amazon Web Services (AWS), Azure, Google Cloud Platform (GCP), Kubernetes, Helm, GitHub, Splunk, DataDog, and many more.
@@ -22,7 +22,7 @@ The core Terraform workflow consists of three stages:
- **Plan:** Terraform creates an execution plan describing the infrastructure it will create, update, or destroy based on the existing infrastructure and your configuration.
- **Apply:** On approval, Terraform performs the proposed operations in the correct order, respecting any resource dependencies. For example, if you update the properties of a VPC and change the number of virtual machines in that VPC, Terraform will recreate the VPC before scaling the virtual machines.
![](<../images/image (215).png>)
![Terraform workflow diagram showing Write, Plan, and Apply stages from configuration to providers](<../images/image (215).png>)
### Terraform Lab
@@ -16,13 +16,13 @@ basic-travisci-information.md
To launch an attack you first need to know how to trigger a build. By default TravisCI will **trigger a build on pushes and pull requests**:
![](<../../images/image (145).png>)
![Travis CI trigger settings with build pushed branches and build pushed pull requests enabled](<../../images/image (145).png>)
#### Cron Jobs
If you have access to the web application you can **set crons to run the build**, this could be useful for persistence or to trigger a build:
![](<../../images/image (243).png>)
![Travis CI cron job settings with branch, interval, options, and add button](<../../images/image (243).png>)
> [!NOTE]
> It looks like It's not possible to set crons inside the `.travis.yml` according to [this](https://github.com/travis-ci/travis-ci/issues/9162).
@@ -31,7 +31,7 @@ If you have access to the web application you can **set crons to run the build**
TravisCI by default disables sharing env variables with PRs coming from third parties, but someone might enable it and then you could create PRs to the repo and exfiltrate the secrets:
![](<../../images/image (208).png>)
![Travis CI pull request security setting for sharing encrypted environment variables with forks](<../../images/image (208).png>)
### Dumping Secrets
@@ -41,7 +41,7 @@ As explained in the [**basic information**](basic-travisci-information.md) page,
- To enumerate the **custom encrypted secrets** the best you can do is to **check the `.travis.yml` file**.
- To **enumerate encrypted files** you can check for **`.enc` files** in the repo, for lines similar to `openssl aes-256-cbc -K $encrypted_355e94ba1091_key -iv $encrypted_355e94ba1091_iv -in super_secret.txt.enc -out super_secret.txt -d` in the config file, or for **encrypted iv and keys** in the **Environment Variables** such as:
![](<../../images/image (81).png>)
![Travis CI environment variables page showing encrypted file IV and key variable names](<../../images/image (81).png>)
### TODO:
@@ -18,7 +18,7 @@ For example, in Github it will ask for the following permissions:
In TravisCI, as in other CI platforms, it's possible to **save at repo level secrets** that will be saved encrypted and be **decrypted and push in the environment variable** of the machine executing the build.
![](<../../images/image (203).png>)
![Travis CI environment variables settings with a masked SUPERSECRET value and branch selector](<../../images/image (203).png>)
It's possible to indicate the **branches to which the secrets are going to be available** (by default all) and also if TravisCI **should hide its value** if it appears **in the logs** (by default it will).
@@ -35,7 +35,7 @@ travis pubkey -r carlospolop/t-ci-test
Then, you can use this setup to **encrypt secrets and add them to your `.travis.yaml`**. The secrets will be **decrypted when the build is run** and accessible in the **environment variables**.
![](<../../images/image (139).png>)
![Terminal output from travis encrypt adding a secure value to the .travis.yml file](<../../images/image (139).png>)
Note that the secrets encrypted this way won't appear listed in the environmental variables of the settings.
@@ -63,7 +63,7 @@ Commit all changes to your .travis.yml.
Note that when encrypting a file 2 Env Variables will be configured inside the repo such as:
![](<../../images/image (170).png>)
![Travis CI environment variables page showing encrypted file IV and key variable names](<../../images/image (170).png>)
## TravisCI Enterprise
@@ -87,7 +87,7 @@ Travis CI Enterprise is an **on-prem version of Travis CI**, which you can deplo
The amount of deployed TCI Worker and build environment OS images will determine the total concurrent capacity of Travis CI Enterprise deployment in your infrastructure.
![](<../../images/image (199).png>)
![Travis CI Enterprise architecture diagram with core services, database, workers, and build environments](<../../images/image (199).png>)
{{#include ../../banners/hacktricks-training.md}}
@@ -4,7 +4,7 @@
## Organization Hierarchy
![](<../../../images/image (151).png>)
![AWS Organizations hierarchy diagram with organization root, organizational units, member accounts, policies, and users](<../../../images/image (151).png>)
### Accounts
@@ -20,9 +20,9 @@ I tested this doing the process in the web page, the permissions indicated previ
During the **creation of the build project** you can indicate a **command to run** (rev shell?) and to run the build phase as **privileged user**, that's the configuration the attacker needs to compromise:
![](<../../../images/image (276).png>)
![AWS CodeBuild buildspec name field set to env during build project creation](<../../../images/image (276).png>)
![](<../../../images/image (181).png>)
![AWS CodeBuild privileged mode checkbox enabled for elevated build privileges](<../../../images/image (181).png>)
### ?`codebuild:UpdateProject, codepipeline:UpdatePipeline, codepipeline:StartPipelineExecution`
@@ -38,7 +38,7 @@ For example, an attacker with those **permissions over a cloudformation bucket**
And the hijack is possible because there is a **small time window from the moment the template is uploaded** to the bucket to the moment the **template is deployed**. An attacker might just create a **lambda function** in his account that will **trigger when a bucket notification is sent**, and **hijacks** the **content** of that **bucket**.
![](<../../../images/image (174).png>)
![CloudFormation template bucket hijack diagram using an attacker-controlled Lambda to modify the uploaded template](<../../../images/image (174).png>)
The Pacu module [`cfn__resouce_injection`](https://github.com/RhinoSecurityLabs/pacu/wiki/Module-Details#cfn__resource_injection) can be used to automate this attack.\
For mor informatino check the original research: [https://rhinosecuritylabs.com/aws/cloud-malware-cloudformation-injection/](https://rhinosecuritylabs.com/aws/cloud-malware-cloudformation-injection/)
@@ -65,7 +65,7 @@ Therefore, users with `ssm:StartSession` will be able to **get a shell inside EC
aws ssm start-session --target "ecs:CLUSTERNAME_TASKID_RUNTIMEID"
```
![](<../../../images/image (185).png>)
![Terminal running aws ssm start-session against an ECS target and receiving a root shell](<../../../images/image (185).png>)
**Potential Impact:** Direct privesc to the `ECS`IAM roles attached to running tasks with `ExecuteCommand` enabled.
@@ -21,7 +21,7 @@ Moreover, in order to **access Internet**, there are some interesting configurat
- You could also have a **NAT gateway** in a **private subnet** so it's possible to **connect to external services** from that private subnet, but it's **not possible to reach them from the outside**.
- The NAT gateway can be **public** (access to the internet) or **private** (access to other VPCs)
![](<../../../../images/image (274).png>)
![AWS VPC networking diagram with public and private subnets, internet gateway, NAT gateway, and cloud access](<../../../../images/image (274).png>)
## VPC
@@ -16,7 +16,7 @@ A Lambda can have **several versions**.\
And it can have **more than 1** version exposed via **aliases**. The **weights** of **each** of the **versions** exposed inside and alias will decide **which alias receive the invocation** (it can be 90%-10% for example).\
If the code of **one** of the aliases is **vulnerable** you can send **requests until the vulnerable** versions receives the exploit.
![](<../../../images/image (223).png>)
![AWS Lambda aliases page showing release alias traffic split between version 2 and version 1](<../../../images/image (223).png>)
### Resource Policies
@@ -122,7 +122,7 @@ Now it's time to find out possible lambda functions to execute:
aws --region us-west-2 --profile level6 lambda list-functions
```
![](<../../../images/image (262).png>)
![AWS Lambda function configuration JSON including function name, runtime, role, handler, and URL config](<../../../images/image (262).png>)
A lambda function called "Level6" is available. Lets find out how to call it:
@@ -130,7 +130,7 @@ A lambda function called "Level6" is available. Lets find out how to call it:
aws --region us-west-2 --profile level6 lambda get-policy --function-name Level6
```
![](<../../../images/image (102).png>)
![Terminal output for aws lambda add-permission granting public function URL invocation](<../../../images/image (102).png>)
Now, that you know the name and the ID you can get the Name:
@@ -138,7 +138,7 @@ Now, that you know the name and the ID you can get the Name:
aws --profile level6 --region us-west-2 apigateway get-stages --rest-api-id "s33ppypa75"
```
![](<../../../images/image (237).png>)
![AWS API Gateway get-stages output showing a stage with method settings and deployment ID](<../../../images/image (237).png>)
And finally call the function accessing (notice that the ID, Name and function-name appears in the URL): [https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6](https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6)
@@ -36,11 +36,11 @@ Inside each folder each log will have a **name following this format**: **`Accou
Log File Naming Convention
![](<../../../../images/image (122).png>)
![CloudTrail log file naming convention showing account ID, region, timestamp, and unique string fields](<../../../../images/image (122).png>)
Moreover, **digest files (to check file integrity)** will be inside the **same bucket** in:
![](<../../../../images/image (195).png>)
![CloudTrail digest file S3 path pattern with bucket name, account ID, region, and digest timestamp](<../../../../images/image (195).png>)
### Aggregate Logs from Multiple Accounts
@@ -81,7 +81,7 @@ Note that in order to allow CloudTrail to send the logs to CloudWatch a **role**
CloudTrail Event History allows you to inspect in a table the logs that have been recorded:
![](<../../../../images/image (89).png>)
![AWS CloudTrail Event history table listing event name, time, source, username, resource type, and resource name](<../../../../images/image (89).png>)
### Insights
@@ -203,7 +203,7 @@ In the past there were some **AWS services that doesn't send logs to CloudTrail*
This way, an **attacker can obtain the ARN of the key without triggering any log**. In the ARN the attacker can see the **AWS account ID and the name**, it's easy to know the HoneyToken's companies accounts ID and names, so this way an attacker can identify id the token is a HoneyToken.
![](<../../../../images/image (93).png>)
![AWS CLI AccessDenied error for describe-fleets exposing the caller ARN in the response](<../../../../images/image (93).png>)
> [!CAUTION]
> Note that all public APIs discovered to not being creating CloudTrail logs are now fixed, so maybe you need to find your own...
@@ -30,7 +30,7 @@ A configuration item or **CI** as it's known, is a key component of AWS Config.
- Lambda feeds back to Config
- If rule has been broken, Config fires up an SNS
![](<../../../../images/image (126).png>)
![AWS Config functioning diagram showing events flowing through Config rules to event targets and investigation](<../../../../images/image (126).png>)
### Config Rules
@@ -46,7 +46,7 @@ In the talk [**Breaking the Isolation: Cross-Account AWS Vulnerabilities**](http
During the talk they specify several examples, such as S3 buckets **allowing cloudtrai**l (of **any AWS** account) to **write to them**:
![](<../../../images/image (260).png>)
![S3 bucket policy JSON allowing the CloudTrail service principal to write objects](<../../../images/image (260).png>)
Other services found vulnerable:
@@ -62,7 +62,7 @@ This is a policy example:
That is the **error** you will find if you uses a **role that doesn't exist**. If the role **exist**, the policy will be **saved** without any errors. (The error is for update, but it also works when creating)
![](<../../../images/image (153).png>)
![AWS IAM edit trust policy page showing an invalid principal error for a non-existent role ARN](<../../../images/image (153).png>)
#### CLI
@@ -108,7 +108,7 @@ or you can access the bucket visiting: `flaws.cloud.s3-us-west-2.amazonaws.com`
If you try to access a bucket, but in the **domain name you specify another region** (for example the bucket is in `bucket.s3.amazonaws.com` but you try to access `bucket.s3-website-us-west-2.amazonaws.com`, then you will be **indicated to the correct location**:
![](<../../../images/image (106).png>)
![S3 XML PermanentRedirect response showing the correct bucket endpoint after querying the wrong region](<../../../images/image (106).png>)
### Enumerating the bucket
@@ -116,11 +116,11 @@ To test the openness of the bucket a user can just enter the URL in their web br
Open to everyone:
![](<../../../images/image (201).png>)
![S3 public bucket XML listing object keys and metadata](<../../../images/image (201).png>)
Private:
![](<../../../images/image (83).png>)
![S3 XML AccessDenied response for a private bucket](<../../../images/image (83).png>)
You can also check this with the cli:
@@ -27,7 +27,7 @@ Moreover, if someone compromise a VM where an automation account script is runni
As it's possible to see in the following image, having Administrator access over the VM it's possible to find in the **environment variables of the process** the URL and secret to access the automation account metadata service:
![](</images/vm_to_aa.jpg>)
![Process Explorer view of an Azure Automation worker process exposing automation account metadata environment variables](</images/vm_to_aa.jpg>)
### `Microsoft.Automation/automationAccounts/jobs/write`, `Microsoft.Automation/automationAccounts/runbooks/draft/write`, `Microsoft.Automation/automationAccounts/jobs/output/read`, `Microsoft.Automation/automationAccounts/runbooks/publish/action` (`Microsoft.Resources/subscriptions/resourcegroups/read`, `Microsoft.Automation/automationAccounts/runbooks/write`)
@@ -20,7 +20,7 @@ These are the **different permissions** [according to the docs](https://learn.mi
There are also some **built-in roles** that can be assigned, and it's also possible to create **custom roles**.
![](/images/registry_roles.png)
![Azure Container Registry built-in roles permissions matrix for managing registry, image, data, policies, and signing actions](/images/registry_roles.png)
### Authentication
@@ -19,7 +19,7 @@ Get-AzureADGroup -Filter "DisplayName eq 'Intune Administrators'"
2. Go to **Devices** -> **All Devices** to check devices enrolled to Intune
3. Go to **Scripts** and click on **Add** for Windows 10.
4. Add a **Powershell script**
- ![](<../../../images/image (264).png>)
- ![Microsoft Intune script settings page for adduser.ps1 with logged-on credentials and signature checks disabled](<../../../images/image (264).png>)
5. Specify **Add all users** and **Add all devices** in the **Assignments** page.
The execution of the script can take up to **one hour**.
@@ -70,7 +70,7 @@ Note that **`iam.serviceAccountKeys.update` won't work to modify the key** of a
If you have the **`iam.serviceAccounts.implicitDelegation`** permission on a Service Account that has the **`iam.serviceAccounts.getAccessToken`** permission on a third Service Account, then you can use implicitDelegation to **create a token for that third Service Account**. Here is a diagram to help explain.
![](https://rhinosecuritylabs.com/wp-content/uploads/2020/04/image2-500x493.png)
![GCP IAM implicit delegation diagram chaining Service Account A, B, and C permissions to obtain an access token](https://rhinosecuritylabs.com/wp-content/uploads/2020/04/image2-500x493.png)
Note that according to the [**documentation**](https://cloud.google.com/iam/docs/understanding-service-accounts), the delegation of `gcloud` only works to generate a token using the [**generateAccessToken()**](https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/generateAccessToken) method. So here you have how to get a token using the API directly:
@@ -6,7 +6,7 @@
**If you are lucky enough you may be able to escape from it to the node:**
![](https://sickrov.github.io/media/Screenshot-161.jpg)
![Kubernetes pod breakout diagram showing attacker OS flow from a container through syscalls to the host kernel](https://sickrov.github.io/media/Screenshot-161.jpg)
### Escaping from the pod
@@ -17,7 +17,7 @@
### Architecture
![](https://sickrov.github.io/media/Screenshot-68.jpg)
![Kubernetes architecture diagram showing control plane components, API server, kubelet, kube-proxy, pods, and worker nodes](https://sickrov.github.io/media/Screenshot-68.jpg)
- **Node**: operating system with pod or pods.
- **Pod**: Wrapper around a container or multiple containers with. A pod should only contain one application (so usually, a pod run just 1 container). The pod is the way kubernetes abstracts the container technology running.
@@ -53,7 +53,7 @@ When a pod creates data that shouldn't be lost when the pod disappear it should
### PKI infrastructure - Certificate Authority CA:
![](https://sickrov.github.io/media/Screenshot-66.jpg)
![Kubernetes CA and PKI diagram showing API server certificates between clients, scheduler, controller manager, kubelet, and etcd](https://sickrov.github.io/media/Screenshot-66.jpg)
- CA is the trusted root for all certificates inside the cluster.
- Allows components to validate to each other.
@@ -394,7 +394,7 @@ There are different types of secrets in Kubernetes
**How secrets works:**
![](https://sickrov.github.io/media/Screenshot-164.jpg)
![Kubernetes secrets diagram showing secret data reaching the API server and being consumed by a pod](https://sickrov.github.io/media/Screenshot-164.jpg)
The following configuration file defines a **secret** called `mysecret` with 2 key-value pairs `username: YWRtaW4=` and `password: MWYyZDFlMmU2N2Rm`. It also defines a **pod** called `secretpod` that will have the `username` and `password` defined in `mysecret` exposed in the **environment variables** `SECRET_USERNAME` \_\_ and \_\_ `SECRET_PASSWOR`. It will also **mount** the `username` secret inside `mysecret` in the path `/etc/foo/my-group/my-username` with `0640` permissions.
@@ -12,7 +12,7 @@ RBACs permission model is built from **three individual parts**:
2. **Subject (User, Group or ServiceAccount) ** The object that will receive the permissions.
3. **RoleBinding\ClusterRoleBinding ** The connection between Role\ClusterRole and the subject.
![](https://www.cyberark.com/wp-content/uploads/2018/12/rolebiding_serviceaccount_and_role-1024x551.png)
![Kubernetes RBAC diagram showing RoleBinding connecting a ServiceAccount subject to Role permissions](https://www.cyberark.com/wp-content/uploads/2018/12/rolebiding_serviceaccount_and_role-1024x551.png)
The difference between “**Roles**” and “**ClusterRoles**” is just where the role will be applied a “**Role**” will grant access to only **one** **specific** **namespace**, while a “**ClusterRole**” can be used in **all namespaces** in the cluster. Moreover, **ClusterRoles** can also grant access to:
@@ -132,7 +132,7 @@ sudo nmap -sS -p 30000-32767 <IP>
Anonymous access to **kube-apiserver API endpoints is not allowed**. But you could check some endpoints:
![](https://www.cyberark.com/wp-content/uploads/2019/09/Kube-Pen-2-fig-5.png)
![Kubernetes API server anonymous access output listing exposed API paths](https://www.cyberark.com/wp-content/uploads/2019/09/Kube-Pen-2-fig-5.png)
### **Checking for ETCD Anonymous Access**
@@ -199,7 +199,7 @@ When a **kubelet read-only port** is exposed, it becomes possible for informatio
An example of how this vulnerability can be exploited involves a remote attacker accessing a specific URL. By navigating to `http://<external-IP>:10255/pods`, the attacker can potentially retrieve sensitive information from the kubelet:
![https://www.cyberark.com/wp-content/uploads/2019/09/KUbe-Pen-2-fig-6.png](https://www.cyberark.com/wp-content/uploads/2019/09/KUbe-Pen-2-fig-6.png)
![Kubelet read-only port response exposing pod information](https://www.cyberark.com/wp-content/uploads/2019/09/KUbe-Pen-2-fig-6.png)
## References
@@ -214,4 +214,3 @@ https://labs.f-secure.com/blog/attacking-kubernetes-through-kubelet
{{#include ../../../banners/hacktricks-training.md}}
@@ -104,7 +104,7 @@ Users can only delegate access to another user in the same organization, regardl
Sign in using an _administrator account_, not your current account CarlosPolop@gmail.com
2. In the Admin console, go to Menu ![](https://storage.googleapis.com/support-kms-prod/JxKYG9DqcsormHflJJ8Z8bHuyVI5YheC0lAp)![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)![](https://storage.googleapis.com/support-kms-prod/ocGtUSENh4QebLpvZcmLcNRZyaTBcolMRSyl) **Apps**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**Google Workspace**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**Gmail**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**User settings**.
2. In the Admin console, go to Menu ![Google Admin console main menu icon](https://storage.googleapis.com/support-kms-prod/JxKYG9DqcsormHflJJ8Z8bHuyVI5YheC0lAp)![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)![Google Admin console apps grid icon](https://storage.googleapis.com/support-kms-prod/ocGtUSENh4QebLpvZcmLcNRZyaTBcolMRSyl) **Apps**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**Google Workspace**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**Gmail**![and then](https://storage.googleapis.com/support-kms-prod/Th2Tx0uwPMOhsMPn7nRXMUo3vs6J0pto2DTn)**User settings**.
3. To apply the setting to everyone, leave the top organizational unit selected. Otherwise, select a child [organizational unit](https://support.google.com/a/topic/1227584).
4. Click **Mail delegation**.
5. Check the **Let users delegate access to their mailbox to other users in the domain** box.