Élévation de privilèges et persistance IAM dans CloudTrail
Comment un attaquant reste dans AWS via IAM (utilisateurs cachés, clés en plus, politiques admin, rôles, fournisseurs d'identité) ce que CloudTrail en montre.
En bref. Après la fuite d'une clé, la priorité de l'attaquant est de ne plus dépendre de cette clé. Les gestes habituels, tous visibles dans CloudTrail sous iam.amazonaws.com : CreateUser → AttachUserPolicy (AdministratorAccess) → CreateAccessKey et CreateLoginProfile pour cet utilisateur. Variantes plus discrètes : une deuxième clé sur un utilisateur existant, un principal supplémentaire dans la politique d'approbation d'un rôle, un nouveau fournisseur SAML/OIDC, une fonction Lambda publique ou des user data EC2 modifiées. Lisez requestParameters pour connaître la cible et responseElements pour les identifiants des nouvelles clés, et supprimez chacun de ces éléments avant de déclarer l'incident clos.
Révoquer l'identifiant divulgué, c'est la partie facile d'un incident AWS. Ce qui tourne mal, c'est la porte dérobée que vous n'avez pas trouvée : celle qui permet au même acteur de revenir une semaine plus tard avec une clé que vous n'avez jamais vue. Cet article passe en revue les techniques de persistance et d'élévation de privilèges IAM qui reviennent sans cesse dans les journaux, et la façon dont chacune apparaît dans CloudTrail.
Les événements IAM sont globaux, et journalisés dans us-east-1
IAM est un service global ; comme la plupart des événements de services globaux, ses événements CloudTrail sont enregistrés avec awsRegion à us-east-1. Si vous n'avez récupéré l'historique des événements que pour la région où tournent vos workloads, vous êtes peut-être passé à côté de toutes les modifications IAM. Vérifiez explicitement cette région.
Les utilisateurs servant de porte dérobée
La version bruyante, et toujours la plus courante :
{"eventSource": "iam.amazonaws.com", "eventName": "CreateUser",
"requestParameters": {"userName": "backup-admin"}}
{"eventName": "AttachUserPolicy",
"requestParameters": {"userName": "backup-admin",
"policyArn": "arn:aws:iam::aws:policy/AdministratorAccess"}}
{"eventName": "CreateAccessKey",
"requestParameters": {"userName": "backup-admin"},
"responseElements": {"accessKey": {"accessKeyId": "AKIA..."}}}
{"eventName": "CreateLoginProfile",
"requestParameters": {"userName": "backup-admin", "passwordResetRequired": false}}
Ce qui la trahit :
- Les quatre appels se suivent à quelques secondes d'intervalle, depuis la même clé et la même IP.
- Le nom est choisi pour se fondre dans le décor :
backup,support,svc-…,terraform, quelque chose qui ressemble à un compte de service. passwordResetRequired: falsesur un profil de connexion créé par un script.- L'utilisateur se connecte ensuite à la console sans MFA (
ConsoleLoginavecadditionalEventData.MFAUsed = "No").
ATT&CK : T1136.003 Create Account: Cloud Account, T1098.003 Additional Cloud Roles.
Des identifiants supplémentaires sur des identités existantes
Plus discret : au lieu de créer un utilisateur, l'attaquant ajoute une deuxième clé d'accès à un utilisateur existant (un utilisateur peut en avoir deux), ou définit un mot de passe console sur un utilisateur de service qui n'en a jamais eu.
CreateAccessKeyoùrequestParameters.userNamediffère du nom d'utilisateur de l'appelant : un principal qui génère une clé pour quelqu'un d'autre, c'est rare en fonctionnement normal.CreateLoginProfileouUpdateLoginProfilesur un utilisateur censé n'avoir qu'un accès programmatique.
Le rapport d'identifiants aide ici : access_key_2_active = true sur un utilisateur qui n'a toujours eu qu'une seule clé, ou password_enabled = true sur un compte de service, méritent qu'on pose la question.
ATT&CK : T1098.001 Additional Cloud Credentials.
Droits administrateur : attachés, inline ou via un groupe
| Technique | Événements | Ce qu'il faut lire |
|---|---|---|
| Politique gérée d'administration | AttachUserPolicy, AttachRolePolicy, AttachGroupPolicy | policyArn = …:policy/AdministratorAccess ou IAMFullAccess |
Politique inline *:* | PutUserPolicy, PutRolePolicy, PutGroupPolicy | policyDocument avec Action: "*" et Resource: "*" |
| Nouvelle version de politique | CreatePolicyVersion (souvent avec setAsDefault: true) | Même vérification du document ; le nom de la politique peut sembler anodin |
| Appartenance à un groupe d'administration | AddUserToGroup | groupName du type Admins |
CreatePolicyVersion mérite l'attention, car il modifie ce qu'accorde une politique existante et considérée comme sûre, sans changer ni son nom ni ses attachements. SetDefaultPolicyVersion peut obtenir le même résultat en revenant à une version plus ancienne et plus permissive.
Approbation de rôle et fédération
Les rôles sont plus difficiles à repérer que les utilisateurs, car rien de nouveau n'apparaît dans la liste des utilisateurs.
UpdateAssumeRolePolicyréécrit la liste de qui peut assumer un rôle. Un attaquant y ajoute son propre ID de compte AWS ou un principal externe. Comparez le nouveaupolicyDocumentau précédent (historique AWS Config, infrastructure as code, ou unGetRoleantérieur).CreateSAMLProvider,CreateOpenIDConnectProvider,AddClientIDToOpenIDConnectProvider,UpdateOpenIDConnectProviderThumbprint: quiconque contrôle le fournisseur d'identité peut obtenir des identifiants de rôle dans le compte via AssumeRoleWithSAML / AssumeRoleWithWebIdentity. ATT&CK : T1484.002 Trust Modification.
Lorsque l'organisation fédère ses accès via un fournisseur d'identité externe, l'attaquant a pu entrer par celui-ci plutôt que par IAM ; pour un accès adossé à Okta, voir oktaforensics.com.
Persistance par le calcul
| Technique | Événements | Pourquoi cela persiste |
|---|---|---|
| Lambda publique | AddPermission avec le principal * ; CreateFunctionUrlConfig avec authType: NONE | N'importe qui peut invoquer du code qui s'exécute avec le rôle de la fonction |
| Code Lambda nouveau ou modifié | CreateFunction, UpdateFunctionCode depuis une clé utilisée de façon interactive | Du code contrôlé par l'attaquant, avec les permissions du rôle |
| User data EC2 | ModifyInstanceAttribute avec userData | Le script s'exécute en root au prochain démarrage |
| Paires de clés SSH | CreateKeyPair, ImportKeyPair | Accès aux instances lancées avec cette paire |
Les déploiements de code Lambda depuis la CI sont normaux. Le même événement émis par la clé à long terme d'un humain à une heure inhabituelle ne l'est pas.
Comment s'assurer de les avoir tous trouvés
- Pivotez sur chaque principal contrôlé par l'attaquant (l'utilisateur de la clé divulguée, les utilisateurs créés, les rôles assumés) et listez leurs écritures réussies (
readOnly = false, pas d'errorCode). - Extrayez chaque nouvel identifiant de clé des réponses de
CreateAccessKeyet pivotez aussi sur ceux-là. - Comparez l'état actuel d'IAM à son état antérieur : rapport d'identifiants, sortie de
aws iam get-account-authorization-details, ou l'état de votre IaC. - Cherchez les tentatives échouées : un
AccessDeniedsurCreateUservous dit ce que l'attaquant a essayé, et si une réussite ultérieure est venue d'un autre principal.
Dans l'analyseur
AWS Forensics dispose d'une règle pour chacune des techniques ci-dessus (« Utilisateur IAM créé », « Clé d’accès créée pour un autre utilisateur », « Mot de passe console défini pour un utilisateur », « Politique d’approbation de rôle modifiée », « Fournisseur d’identité ajouté ou modifié », « Utilisateur ajouté à un groupe d’administration », « Fonction Lambda exposée publiquement », « Fonction Lambda créée ou code mis à jour », « User data EC2 modifiées », « Paire de clés SSH créée ou importée », « Politique administrateur attachée », « Politique accordant un accès total (:) »), et la checklist de remédiation nomme précisément les utilisateurs, clés et rôles à nettoyer. La référence des événements à surveiller les liste avec leurs ID ATT&CK, et le déroulé d'incident fictif montre la création puis la découverte d'un administrateur servant de porte dérobée.