vulncheck-oss / go-exploit

A Go-based Exploit Framework
https://pkg.go.dev/github.com/vulncheck-oss/go-exploit
Apache License 2.0
308 stars 29 forks source link

Added Logic to Report Custom Exploit Flags #248

Closed j-baines closed 1 month ago

j-baines commented 1 month ago

Currently, one of our stated goals it to make interoperability with other software easy. One thing that breaks that is when an exploit defines command line parameters that aren't generally well known. What I don't mean is like "lhost", "rhost", "proxy", etc. Those are all well known and easily mapped out to specific exploit types.

I'm talking about this scenario. Consider our implementation for CVE-2024-45507:

func main() {
    httpServer := httpservefile.GetInstance()
    httpServer.CreateFlags()

    flag.StringVar(&httpServer.HTTPAddr, "httpAddr", "", "The address the HTTP server should bind to")
    flag.IntVar(&httpServer.HTTPPort, "httpPort", 8080, "The port the HTTP server should bind to")
    flag.StringVar(&globalHostname, "hostname", "localhost", "Hostname to use if not provided using rhost (default to rhost value or localhost)")

    supportedC2 := []c2.Impl{
        c2.SSLShellServer,
        c2.SimpleShellServer,
    }
    conf := config.NewRemoteExploit(
        config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true},
        config.CodeExecution, supportedC2, "Apache", []string{"OFBiz"},
        []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80)

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

Here we see the exploit define three flags. One of which is totally specific to the exploit (hostname) and two that are hooked into HTTPServeFile logic (we used httpservefile as the http server for this one... meta I actually like, but I digress).

From a "Let's use this programmatically to pwn the world" perspective, these are not great because (outside of the horrible --help menu) there is no way to discover them! So in this patch, I implement a way to bubble these three command line options into the --details view.

First, let me share our CVE-2024-45507 was redefined (the diff is sort of messy since I moved stuff, so this is likely easier):

func main() {
    supportedC2 := []c2.Impl{
        c2.SSLShellServer,
        c2.SimpleShellServer,
    }
    conf := config.NewRemoteExploit(
        config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true},
        config.CodeExecution, supportedC2, "Apache", []string{"OFBiz"},
        []string{"cpe:2.3:a:apache:ofbiz"}, "CVE-2024-45507", "HTTP", 80)

    // This exploit requires an HTTP server so hijack the HTTPServeFile server
    httpServer := httpservefile.GetInstance()
    httpServer.CreateFlags()

    // Newer OFBiz requires the `host` field contain a hostname and not an IP address.
    // If the user provides a hostname using `rhost` we will use that, otherwise
    // we'll use this variable which is defaulted to `localhost` - the user can
    // alter it on the command line.
    conf.CreateStringFlag("hostname", "localhost", "Hostname to use if not provided using rhost (default to rhost value or localhost)")
    conf.CreateStringVarFlag(&httpServer.HTTPAddr, "httpAddr", "", "The address the HTTP server should bind to")
    conf.CreateIntVarFlag(&httpServer.HTTPPort, "httpPort", 8080, "The port the HTTP server should bind to")

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

Here you can see that instead of flag I'm using conf.Create*Flag. This actually eliminates the use of the globalHostname variable. Instead we using conf.GetStringFlag("hostname"):

 func (sploit OFBizGroovyFetch) CheckVersion(conf *config.Config) exploit.VersionCheckType {
-       hostname := globalHostname
+       hostname := conf.GetStringFlag("hostname")
        if net.ParseIP(conf.Rhost) == nil {
                hostname = conf.Rhost
        }
@@ -191,7 +184,7 @@ func hostXMLFile(filename string, cmd string) {

 func triggerExploit(conf *config.Config, decoratorURL string) bool {
        headers := map[string]string{
-               "Host": fmt.Sprintf("%s:%d", globalHostname, conf.Rport),
+               "Host": fmt.Sprintf("%s:%d", conf.GetStringFlag("hostname"), conf.Rport),
        }

Additionally, --details now knows all about these three variables and their types (see CustomFlags):

albinolobster@mournland:~/initial-access/feed/cve-2024-45507$ ./build/cve-2024-45507_linux-arm64 --details --log-json | jq
{
  "time": "2024-10-04T13:04:03.285376637-04:00",
  "level": "SUCCESS",
  "msg": "Implementation Details",
  "ExploitType": "CodeExecution",
  "AssetDetection": true,
  "VersionScanner": true,
  "Exploitation": true,
  "SupportedC2": [
    "SSLShellServer",
    "SimpleShellServer"
  ],
  "Vendor": "Apache",
  "Products": [
    "OFBiz"
  ],
  "CPE": [
    "cpe:2.3:a:apache:ofbiz"
  ],
  "CVE": "CVE-2024-45507",
  "Protocol": "HTTP",
  "DefaultPort": 80,
  "CustomFlags": [
    {
      "Name": "hostname",
      "Type": "string",
      "Default": "localhost"
    },
    {
      "Name": "httpAddr",
      "Type": "string",
      "Default": ""
    },
    {
      "Name": "httpPort",
      "Type": "int",
      "Default": "8080"
    }
  ]
}

The actual implementation in config.go is not actually all that different from how flag works. They go the extra mile to mask types (although at the end of the day, they still have to define get/set for every type anyways and require type-oriented function anyways), but otherwise no big surprises. The parameters are stored in maps in config.go for easy lookup... and that's about it.