Aller au contenu

CloudTrail et GuardDuty — la baseline qui vous donne du forensique et les findings qui comptent vraiment

CloudTrail multi-régions vers S3 avec KMS et Object Lock, trail Organizations contre trail par compte, findings GuardDuty qui justifient une action contre le bruit de fond, EventBridge pour la réponse automatique. Voici le montage.

Une réponse à incident sur un compte client en 2025 a déraillé pour une raison : les logs CloudTrail étaient activés, mais le bucket S3 vers lequel ils écrivaient était dans le même compte que les identifiants IAM compromis. L’attaquant avait s3:DeleteObject sur le bucket via le rôle trop large qu’il avait volé, a supprimé les fichiers de trail pour la fenêtre de quatre heures où il était actif, et l’équipe forensique n’a eu que les événements partiels restant dans l’historique d’événements CloudTrail (qui conserve 90 jours d’événements de gestion dans la console mais rien de plus). La baseline ci-dessous suppose que le compte d’où elle journalise peut être compromis, donc les logs vivent ailleurs avec les bonnes clés et les bons verrous. GuardDuty est le second morceau — il produit des findings en continu, la plupart sont du bruit, et la question est lesquels valent qu’on réveille quelqu’un.

Ce qui se passe

CloudTrail enregistre les appels d’API. Chaque RunInstances, chaque CreateUser, chaque GetObject (si les data events sont activés) génère une entrée JSON. Le trail par défaut capture les événements de gestion du compte où il vit, écrit vers un bucket S3, et c’est la baseline minimale. Les trous de cette baseline minimale :

  • Multi-régions — le trail par défaut est par région. Un appel d’API dans us-west-2 est invisible depuis un trail us-east-1 à moins que le trail ne soit configuré multi-régions. Les attaquants pivotent régulièrement vers des régions obscures précisément parce que la surveillance y est plus faible.
  • Inter-comptes — le bucket S3 vit dans le compte source. Si le compte source est compromis, le trail est supprimable. Le correctif est un compte sécurité/logging séparé qui possède le bucket ; le trail du compte de charge y écrit via une politique de bucket inter-comptes.
  • Chiffrement KMS + Object Lock — le chiffrement côté serveur est par défaut mais avec la clé gérée par le propriétaire du bucket. Avec une CMK KMS gérée par le client dans le compte de logging et Object Lock en mode compliance, les fichiers de trail deviennent immuables pendant la période de rétention. Même root dans le compte de charge ne peut pas les réécrire ni les supprimer.
  • Trail Organizations contre trail par compte — CloudTrail Organizations capture chaque compte membre dans un seul trail, dans le compte de gestion ou un administrateur délégué. L’avantage est la cohérence (chaque compte membre est auditable depuis un seul endroit) ; l’inconvénient est la centralisation du risque (un seul trail à compromettre). Pour la plupart des AWS Organizations, la bonne réponse est les deux — un trail Organizations dans le compte de logging plus des trails par compte pour les charges qui ont besoin de leur propre rétention.

GuardDuty est la couche de détection de menaces. Il consomme les VPC Flow Logs, CloudTrail, les logs de requêtes DNS (et les logs d’audit Kubernetes si activés), les passe contre la base de threat-intel d’AWS et des modèles comportementaux, et produit des findings. Les catégories qui méritent vraiment une action :

  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS — identifiants d’un rôle d’instance utilisés depuis une IP qui n’est pas une instance EC2. C’est le playbook SSRF-vers-exfiltration en vol.
  • CredentialAccess:IAMUser/AnomalousBehavior — un principal IAM exécutant des actions inhabituelles par rapport à son patron (lister des buckets S3 qu’il n’a jamais touchés).
  • Backdoor:EC2/C&CActivity.B!DNS — requête DNS d’une instance EC2 vers un domaine de commande-et-contrôle connu.
  • PenTest:IAMUser/KaliLinux — appels d’API avec un user agent Kali Linux. Pas forcément malveillant (les équipes sécurité l’utilisent), mais mérite vérification.
  • Recon:IAMUser/MaliciousIPCaller — appel d’API depuis une IP de la liste de blocage threat-intel.
  • CryptoCurrency:EC2/BitcoinTool.B!DNS — instance EC2 résolvant des domaines de pools de minage. Presque toujours une instance compromise faisant tourner le mineur d’un attaquant.

Le bruit de fond, c’est Recon:EC2/PortProbeUnprotectedPort et Recon:EC2/Portscan — internet sonde constamment chaque IP publique. Ces findings sont réels mais pas actionnables en isolation ; ils ne comptent que corrélés avec une exploitation réussie.

Comment vérifier

La vérification sur CloudTrail :

aws cloudtrail describe-trails \
  --query 'trailList[].[Name,IsMultiRegionTrail,IsOrganizationTrail,IncludeGlobalServiceEvents,KmsKeyId,LogFileValidationEnabled,S3BucketName]' \
  --output table

Chaque colonne doit être renseignée. IsMultiRegionTrail: true, KmsKeyId non vide, LogFileValidationEnabled: true, S3BucketName pointant vers un bucket dans un compte différent.

Puis vérifier la configuration du bucket :

aws s3api get-object-lock-configuration --bucket security-trail-prod
aws s3api get-bucket-policy --bucket security-trail-prod

Object Lock en mode COMPLIANCE avec une période de rétention (généralement 365 jours pour SOC 2, 7 ans pour HIPAA). La politique du bucket refuse s3:DeleteObject et s3:PutBucketPolicy à tout sauf un rôle administratif étroitement scopé dans le compte de logging.

Pour GuardDuty :

aws guardduty list-detectors
aws guardduty get-detector --detector-id <id>
aws guardduty list-findings --detector-id <id> \
  --finding-criteria '{"Criterion":{"severity":{"Gte":7}}}' \
  --max-results 50

Le filtre sévérité-7-et-plus coupe le bruit. La cible de nettoyage : il devrait y avoir zéro finding ouvert avec sévérité ≥ 7 dans un compte sain. Les findings sévérité 5-7 nécessitent un triage mais sont de routine.

La correction

Le montage Organizations (lancé depuis le compte de gestion, en déléguant l’administration à un compte de logging) :

  1. Créer la CMK KMS dans le compte de logging avec une politique de clé qui autorise CloudTrail à chiffrer et l’équipe sécurité à déchiffrer. La politique refuse explicitement tout autre principal — même root dans les comptes de charge.

    aws kms create-key \
      --description "Chiffrement CloudTrail" \
      --policy file://trail-key-policy.json \
      --region us-east-1
  2. Créer le bucket S3 dans le compte de logging avec Object Lock activé à la création (c’est un coup unique — Object Lock ne peut pas être ajouté à un bucket existant sans contacter le support AWS) :

    aws s3api create-bucket \
      --bucket security-trail-prod \
      --region us-east-1 \
      --object-lock-enabled-for-bucket
    
    aws s3api put-object-lock-configuration \
      --bucket security-trail-prod \
      --object-lock-configuration '{
        "ObjectLockEnabled": "Enabled",
        "Rule": {
          "DefaultRetention": {
            "Mode": "COMPLIANCE",
            "Days": 365
          }
        }
      }'
  3. Appliquer une politique de bucket qui accorde à CloudTrail dans chaque compte membre Organizations le s3:PutObject vers son préfixe, et refuse la suppression :

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AWSCloudTrailWrite",
          "Effect": "Allow",
          "Principal": {"Service": "cloudtrail.amazonaws.com"},
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::security-trail-prod/AWSLogs/o-abc123/*",
          "Condition": {
            "StringEquals": {
              "aws:SourceOrgID": "o-abc123",
              "s3:x-amz-acl": "bucket-owner-full-control"
            }
          }
        },
        {
          "Sid": "DenyDelete",
          "Effect": "Deny",
          "Principal": "*",
          "Action": ["s3:DeleteObject", "s3:DeleteObjectVersion"],
          "Resource": "arn:aws:s3:::security-trail-prod/*"
        }
      ]
    }
  4. Créer le trail Organizations depuis le compte de gestion, pointant vers le bucket du compte de logging et la CMK KMS :

    aws cloudtrail create-trail \
      --name org-trail-prod \
      --s3-bucket-name security-trail-prod \
      --kms-key-id arn:aws:kms:us-east-1:111111111111:key/abc-... \
      --is-multi-region-trail \
      --is-organization-trail \
      --include-global-service-events \
      --enable-log-file-validation
    
    aws cloudtrail start-logging --name org-trail-prod
  5. Activer GuardDuty pour l’organisation avec auto-activation sur les nouveaux comptes :

    aws organizations register-delegated-administrator \
      --account-id 222222222222 \
      --service-principal guardduty.amazonaws.com
    
    # puis depuis le compte de logging :
    aws guardduty enable-organization-admin-account --admin-account-id 222222222222
    aws guardduty update-organization-configuration \
      --detector-id <id> \
      --auto-enable-organization-members ALL
  6. Câbler les findings haute sévérité à EventBridge pour la réponse automatique. La règle ci-dessous route les findings sévérité ≥ 7 vers un topic SNS qui page l’astreinte :

    GuardDutyHighSeverityRule:
      Type: AWS::Events::Rule
      Properties:
        Name: gd-high-severity
        EventPattern:
          source: ["aws.guardduty"]
          detail-type: ["GuardDuty Finding"]
          detail:
            severity: [{"numeric": [">=", 7]}]
        Targets:
          - Arn: !Ref SecurityPageSnsTopic
            Id: gd-page
  7. Pour le type de finding spécifique UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS, ajouter une cible Lambda qui isole automatiquement l’instance — révoquer le rôle IAM, snapshooter les volumes EBS pour forensique, et arrêter l’instance :

    GuardDutyExfiltrationRule:
      Type: AWS::Events::Rule
      Properties:
        Name: gd-credential-exfil
        EventPattern:
          source: ["aws.guardduty"]
          detail-type: ["GuardDuty Finding"]
          detail:
            type: ["UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS"]
        Targets:
          - Arn: !GetAtt IsolateInstanceLambda.Arn
            Id: isolate

    La logique de la Lambda est courte (50 lignes de Python) et le mode de défaillance est contenu — isoler une instance légitime pendant un faux positif est réversible.

Trois règles sur chaque déploiement CloudTrail/GuardDuty que nous menons :

Le trail part vers un compte différent. Un trail dans le même compte est forensiquement inutile contre un attaquant qui pivote dans le rôle de la charge. Le bucket inter-comptes plus Object Lock plus CMK KMS est la baseline.

Sévérité ≥ 7 sur GuardDuty est de qualité paging. La bande 5-7 est du triage de routine ; sous 5, c’est du bruit. La famille Recon sous sévérité 5 n’est intéressante que comme patron de fond, pas comme paging.

Réponse automatique sur un ensemble étroit de findings seulement. L’exfiltration d’identifiants est le déclencheur canonique d’auto-isolation. Une réponse automatique plus large (auto-désactivation des utilisateurs IAM, auto-révocation des rôles) accumule les verrouillages par faux positifs et finit désactivée dans le trimestre. La retenue est ce qui maintient la règle en service.

Le déploiement complet sur une AWS Organization neuve tient en deux jours concentrés — l’essentiel est la matrice de trust IAM entre les comptes de charge, sauvegarde et DR. La récompense, c’est un enregistrement forensique qui survit à un compte de charge compromis et un flux de paging qui contient les findings qui comptent vraiment.


Stack Harbor opère le nettoyage IAM, la baseline CloudWatch, AWS Backup et le rythme de contrôle des coûts dans le cadre de Gestion AWS et des Environnements infogérés en continu. Si le bucket de trail est dans le même compte que la charge, commencez par une consultation d’infrastructure.