Building a Go scanner to search externally reachable StarOffice Managers

This is a little hobby project of mine that I started to get some experience with Go.

DISCLAIMER: I am not a programmer and not responsible for your eye sores reading this code. Please do critique the script.

Background

I want to kick this post off with a little background information about why I decided to make a Go scanner.

On September 18th, 2018, I discovered a way to remotely execute code using a built-in functionality of the Apache UNO API using Python. The functionality caught my eye while initially researching DCOM and looking at different applications for possible new DCOM vulnerabilities. This Apache UNO API functionality is available for use in Apache OpenOffice and LibreOffice.

The security issue was reported to Apache as well as the LibreOffice security teams, both teams did not deem this to be an issue since this is an edge case.

Even though my view on this differs from the view of the mentioned security teams it was decided to not release the proof-of-concept code that shows how to use the remote execution of arbitrary system commands (if you have some experience with Python you should be able to figure this out with the information in the blogpost I wrote.) until a vendor advisory is available.

Scanner overview

Before building the Go scanner I made sure to list the scanner’s functionalities and objectives.

Scanner functionalities

The project started with me creating a small set of functionalities it should have:

  • Scan a single host or scan a complete subnet;
  • Just grab specific banners (for now);
  • Output the results to a file;

The above functionalities were the bare minimum that the scanner should have.

Scanner objectives

For the scanner I am describing in this post specifically, I just wanted to be able to scan for possible StarOffice Managers that are externally reachable. With this in mind it was easier for me to focus on the script and testing it in a virtual environment.

Building the scanner

Since I had no prior experience using Go, I dived right into it and approached the project as I would with any Python project; Look for similar solutions, reuse the code, and modify it to fit my needs.

Accepting command-line arguments

The project started with me Googling how to make Go accept the arguments given with the script. I found the flag package that is probably the best practice use, but also found a nice StackOverflow post explaining how to use the os package which contains a variable (Args) that holds the command-line arguments.

I tested this for a bit, assigning the command-line arguments to a variable and printing these to Stdout with the following code snippet:

package main 
import (
    "fmt"
    "os"
    "strconv"
)
func main() {
    fmt.Println("----- Go Banner Grabber -----\n\n")
    host := os.Args[1]
    fmt.Printf("[+] Scanning host at: %s\n", host) 
}

The above code is of course, a very simple use case. You will see that the end result is a bit more refined.

At this point I knew that I would have to accept the following command-line arguments for the base functionalities of the script:

  • The host or subnet to scan;
  • Starting port;
  • Ending port;
  • Banner to look for;

With the above command-line arguments I could also do the little verification that the right amount of arguments was given (bad practice user input check incoming):

// Check if arguments are given by the user, exit if less than 4 
    if len(os.Args) < 4 {
        fmt.Println("Usage: " + os.Args[0] + " [address or subnet] [start_port] [end_port]")
    os.Exit(1)
}

Setting up TCP connections

This part I struggled with for a little while, luckily there were already some Go scanners around from which I could borrow some code (see bottom of this post for the links to these scanners).

Go has a package for setting up all kinds of connections, it’s pretty well documented and not too hard to implement. The simplest example of setting up a connection is also described on the page of the package:

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
    // handle error
}

The ‘err’ variable is used to handle errors that could occur whilst setting up the connection. Because I didn’t want to create the little ‘if’ statement everytime I needed to catch something, I peeked some existing scanner’s code and also implemented the following function to nicely handle this with just a single line right under the assignment of the connection variable:

// Function to catch errors
func errorCheck(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

Tying it together

This basic functionality works like a charm, I snooped some code from another scanner to ensure that I could connect to different ports given as command-line arguments. The following code is taken from the end result, the comments should give some insight in what the code does:

// Loop over the given range of ports 
for port := start_port; port <= end_port; port++ {
    // Assign the address and port to variable 'host' 
    host := fmt.Sprintf("%s%s%d", address, ":", port) 
    // Set up the connection with the host 
    conn, err := net.DialTimeout("tcp", host, 10*time.Millisecond) 
    // Continue when no errors occurred 
    errorCheck(err) 
    // Add a timeout 
    conn.SetReadDeadline(time.Now().Add(40 * time.Second)) 
    // Create a buffer to receive data 
    buff := make([]byte, 1024) 
    // Read in data from the connection with the host 
    stored_buff, _ := conn.Read(buff) 
    // Check if the banner indicating the StarOffice manager is present 
    if strings.Contains(string(buff[:stored_buff]), banner) { 
        // Let the user know when there is a hit 
        fmt.Printf("[+] StarOffice Manager at: %s:%d\n", address, port) 
        // Set a timeout so the script doesn't hang
        conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
    }
}

Adding support to scan subnets

This part is a very simple and small implementation from an example that I found. I changed it a little bit to allow the user of the scanner give a subnet or a host and scan an entire range if a subnet is given as a command-line argument:

// If the first argument contains a slash it is an indicator that a subnet must be scanned instead of a single host 
// If there is no slash in the first argument it is assumed that just the given host must be scanned 
if strings.Contains(os.Args[1], string('/')) {
    // Define variable to store the number of hosts 
    var total_hosts int 
    // Define a variable to store the IP addresses in array of strings (slices actually but whatever) 
    var hosts []string 

    // Assign the given subnet to the subnet variable 
   subnet = os.Args[1] 
    // Parse the given subnet with the CIDR package 
    ip, ipnet, err := net.ParseCIDR(subnet) 
    // Small error handling for fatal errors, stop the script if these occur 
    errorCheck(err) 
    // Iterate over the subnet mask given, the 'inc' function is used to dissect the IP-addresses 
    for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { 
        // Increment the number of hosts for very iteration 
        total_hosts++ 
        // Add the IP-address that is present in the subnet to the array 
        hosts = append(hosts, net.IP.String(ip)) 
    }

The ‘inc’ function handles the increment function that is needed to get all addresses in a given subnet.

Writing the result to a file

Like other scripting and/or programming languages there is already a package available that is able to create, modify and store data in files. The function made to handle this task is pretty basic:

// Define the output file, file descriptors and permissions 
output_file, err := os.OpenFile("result", os.O_APPEND|os.O_WRONLY, 0644) 
errorCheckFile(err) 
// Write the result to the output file
 write_output, err := output_file.WriteString(result) 
if err != nil {
    fmt.Println(write_output, err) 
} 
// Close the file after writing 
output_file.Close()

Testing the scanner

With everything in place the scanner can be tested, I created a virtual machine and placed this machine in the same testing environment as the machine running the scanner. This way I can test the host scan, as well as the subnet scanning functionality:

Scanning the machine on the virtual network using just the host and port ranges 1000-65535
Scanning the DoD range 6.0.0.0/8 using port range 2000-65535

NOTE: The Department of Defense is part of a bug bounty program that is listed on HackerOne. So far I have had no results using the banner of the Apache UNO API, but who knows 🙂

Performance

The overall performance is pretty good, until you are going to scan for just the Apache UNO API. For some reason this banner displays after anything between 30-40 seconds after connecting (tested using netcat). For this reason I had to set the timeout delay to 40 seconds to make sure that it would grab the banner if being present, this means that it will add this delay for every open port so be advised when adding a delay. Normally you would be able to scan banners with a timeout of 5 seconds (maybe even less, I set the default on 5 seconds in the final script).

The code snippet that I modified from the link earlier also uses workers to balance the load and divide the ports to scan over the workers, so this adds a nice performance boost as well (you can find this part in the script itself).

Conclusion

In the end I learned a little bit of Go while creating a script that is somewhat usable (to me). I secretly hope to uncover some externally reachable StarOffice Managers with this script as I would like Apache to take another look at their API and maybe rethink about how to use the method to execute system commands.

You can find the complete script on my Github page. Feel free to play around with it or to reuse/modify/critique the code on there!