-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathhttpservefile.go
256 lines (221 loc) · 8.42 KB
/
httpservefile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// httpservefile c2 spawns an HTTP or HTTPS server and hosts arbitrary user-provided files. The normal use case
// is for an exploit to curl/wget the file and execute it. This is useful to spawn connections to other
// tools (e.g. Metasploit, nc, etc.) or to go-exploit. This is not a traditional "c2" but serves as a useful
// backend that logically plugs into our c2 design.
//
// Files are provided on the command line as a comma delimited string. For example:
//
// -httpServeFile.FilesToServe ./build/reverse_shell_windows-arm64.exe,./build/reverse_shell_linux-amd64
//
// The above will load two files: a windows reverse shell and a linux reverse shell. This c2 will then
// generate random names for the files and host them on an HTTP / HTTPS server. To interact with the
// files from an implementing exploit, you can fetch the filename to random name mapping using
// GetRandomName(). For example:
//
// httpservefile.GetInstance().GetRandomName(linux64)
//
// Where linux64 is a variable that contains "reverse_shell_linux-amd64".
//
// If you are only hosting one file, then GetRandomName("") will also return your one file.
//
// Files can also be provided programmatically via the AddFile function (must be called before run).
package httpservefile
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"net/http"
"os"
"path"
"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/random"
)
type HostedFile struct {
// The user provided filename
RealName string
// A randomly generated filename to serve
RandomName string
// The file's data
FileData []byte
}
type Server struct {
// The HTTP address to bind to
HTTPAddr string
// The HTTP port to bind to
HTTPPort int
// Set to the Server field in HTTP response
ServerField string
// Indicates if TLS should be enabled
TLS 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
// Loaded certificate
Certificate tls.Certificate
// A map of hosted files
HostedFiles map[string]HostedFile // RealName -> struct
// A comma delimited list of all the files to serve
FilesToServe string
}
var singleton *Server
// A basic singleton interface for the c2.
func GetInstance() *Server {
if singleton == nil {
singleton = new(Server)
// init hosted files map
singleton.HostedFiles = make(map[string]HostedFile)
}
return singleton
}
// User options for serving a file over HTTP as the "c2".
func (httpServer *Server) CreateFlags() {
// some c2 are really just chained implementations (e.g. httpserveshell is httpservefile plus simpleshell or sslshell).
// so first check if these values have already been generated
if flag.Lookup("httpServeFile.FilesToServe") == nil {
flag.StringVar(&httpServer.FilesToServe, "httpServeFile.FilesToServe", "", "A comma delimited list of all the files to serve")
flag.StringVar(&httpServer.ServerField, "httpServeFile.ServerField", "Apache", "The value to insert in the HTTP server field")
flag.BoolVar(&httpServer.TLS, "httpServeFile.TLS", false, "Indicates if the HTTP server should use encryption")
flag.StringVar(&httpServer.PrivateKeyFile, "httpServeFile.PrivateKeyFile", "", "A private key to use with the HTTPS server")
flag.StringVar(&httpServer.CertificateFile, "httpServeFile.CertificateFile", "", "The certificate to use with the HTTPS server")
}
}
// load the provided files into memory, stored in a map, and loads the tls cert if needed.
func (httpServer *Server) Init(channel channel.Channel) bool {
if channel.IsClient {
output.PrintFrameworkError("Called C2HTTPServer as a client. Use lhost and lport.")
return false
}
switch {
case channel.HTTPPort == 0 && channel.Port != 0:
// must be stand-alone invocation of HTTPServeFile
httpServer.HTTPAddr = channel.IPAddr
httpServer.HTTPPort = channel.Port
case channel.HTTPPort != 0:
// must be used with another C2
httpServer.HTTPAddr = channel.HTTPAddr
httpServer.HTTPPort = channel.HTTPPort
default:
output.PrintFrameworkError("Called HTTPServeFile without specifying a bind port.")
return false
}
// split the provided files, read them in, and store them in the map
files := strings.Split(httpServer.FilesToServe, ",")
for _, file := range files {
if len(file) == 0 {
continue
}
output.PrintfFrameworkStatus("Loading the provided file: %s", file)
fileData, err := os.ReadFile(file)
if err != nil {
output.PrintFrameworkError(err.Error())
return false
}
// remove the path from the name (check for / and \)
shortName := file
pathSepIndex := strings.LastIndex(shortName, "/")
if pathSepIndex != -1 {
shortName = shortName[pathSepIndex+1:]
}
pathSepIndex = strings.LastIndex(shortName, `\`)
if pathSepIndex != -1 {
shortName = shortName[pathSepIndex+1:]
}
hosted := HostedFile{
RealName: shortName,
RandomName: random.RandLetters(12),
FileData: fileData,
}
output.PrintfFrameworkDebug("Added %s as %s", hosted.RealName, hosted.RandomName)
httpServer.HostedFiles[shortName] = hosted
}
if httpServer.TLS {
var ok bool
var err error
if len(httpServer.CertificateFile) != 0 && len(httpServer.PrivateKeyFile) != 0 {
httpServer.Certificate, err = tls.LoadX509KeyPair(httpServer.CertificateFile, httpServer.PrivateKeyFile)
if err != nil {
output.PrintfFrameworkError("Error loading certificate: %s", err.Error())
return false
}
} else {
output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate")
httpServer.Certificate, ok = encryption.GenerateCertificate()
if !ok {
return false
}
}
}
return true
}
// Adds a file to the server. A route will be created for "randomName" when run() is executed.
func (httpServer *Server) AddFile(realName string, randomName string, data []byte) {
hostMe := HostedFile{RealName: realName, RandomName: randomName, FileData: data}
httpServer.HostedFiles[realName] = hostMe
}
// start the HTTP server and listen for incoming requests for `httpServer.FileName`.
func (httpServer *Server) Run(timeout int) {
if len(httpServer.HostedFiles) == 0 {
output.PrintFrameworkError("No files provided via httpServeFile.FilesToServe or programmatically")
return
}
// set up handlers for each of the files
for _, hosted := range httpServer.HostedFiles {
http.HandleFunc("/"+hosted.RandomName, func(writer http.ResponseWriter, req *http.Request) {
output.PrintfFrameworkStatus("Connection from %s requested %s", req.RemoteAddr, req.URL.Path)
writer.Header().Set("Server", httpServer.ServerField)
name := path.Base(req.URL.Path)
// cannot used hosted as the values move on with the loop
for _, selected := range httpServer.HostedFiles {
if selected.RandomName == name {
http.ServeContent(writer, req, selected.RandomName, time.Time{}, bytes.NewReader(selected.FileData))
return
}
}
writer.WriteHeader(http.StatusNotFound)
})
}
connectionString := fmt.Sprintf("%s:%d", httpServer.HTTPAddr, httpServer.HTTPPort)
go func() {
if httpServer.TLS {
output.PrintfFrameworkStatus("Starting an HTTPS server on %s", connectionString)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{httpServer.Certificate},
// We have no control over the SSL versions supported on the remote target. Be permissive for more targets.
MinVersion: tls.VersionSSL30,
}
server := http.Server{
Addr: connectionString,
TLSConfig: tlsConfig,
}
defer server.Close()
_ = server.ListenAndServeTLS("", "")
} else {
output.PrintfFrameworkStatus("Starting an HTTP server on %s", connectionString)
_ = http.ListenAndServe(connectionString, nil)
}
}()
// let the server run for timeout seconds
time.Sleep(time.Duration(timeout) * time.Second)
// We don't actually clean up anything, but exiting c2 will eventually terminate the program
output.PrintFrameworkStatus("Shutting down the HTTP Server")
}
// Returns the random name of the provided filename. If filename is empty, return the first entry.
func (httpServer *Server) GetRandomName(filename string) string {
if len(filename) == 0 {
for _, hosted := range httpServer.HostedFiles {
return hosted.RandomName
}
}
hosted, found := httpServer.HostedFiles[filename]
if !found {
output.PrintfFrameworkError("Requested a file that doesn't exist: %s", filename)
return ""
}
return hosted.RandomName
}