-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathrequest.go
More file actions
459 lines (400 loc) · 15 KB
/
request.go
File metadata and controls
459 lines (400 loc) · 15 KB
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
package pkg
/* Documentation
The `request.go` file contains several functions related to making HTTP requests and handling connections. Let's go through each function:
The `buildRequest` function takes a URL string (`u`) and a `structs.Config` object (`conf`) as input and constructs an `http.Request` object based on the provided information. It determines whether to issue a GET or POST request based on the presence of data in the configuration. It sets headers and query parameters according to the configuration values, and returns the constructed request.
The `setQuery` function takes an `http.Request` object (`req`), a key string (`key`), and a payload string (`payload`) as input. It sets the specified key-value pair as a query parameter in the request's URL.
The `setPost` function takes an `http.Request` object (`req`), a key string (`key`), and a payload string (`payload`) as input. It sets the specified key-value pair as a POST parameter in the request's body. It returns a new `http.Request` object with the updated body.
The `setHeader` function takes an `http.Request` object (`req`), a key string (`key`), and a payload string (`payload`) as input. It sets the specified key-value pair as a header in the request.
The `dialConnection` function takes a scheme string (`scheme`) and a host string (`host`) as input. It establishes a network connection using the appropriate protocol (HTTP or HTTPS) based on the scheme. It returns a `net.Conn` object representing the connection.
The `doRequest` function takes an `http.Request` object (`req`) as input and performs an HTTP request using the provided request object. It handles scenarios where there are host header manipulations or the request needs to be sent through a proxy. The function automatically follows redirects (301, 302, 303, 307, 308) up to a maximum depth of 10 redirects, analyzing the final response after following redirects. It returns the response body, headers, status code, dump (string representation of the response), and any error that occurred during the request.
Overall, the file contains functions for building requests, setting query parameters and headers, establishing network connections, and performing HTTP requests with various configurations.
*********/
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"
"github.com/Hackmanit/TInjA/pkg/structs"
)
func buildRequest(u string, conf structs.Config) (req *http.Request, err error) {
// Shall a GET or POST request be issued?
if conf.Data != "" {
req, err = http.NewRequest("POST", u, bytes.NewBufferString(conf.Data))
if err != nil {
Print("buildRequest: NewRequest: "+err.Error()+"\n", Red)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
} else {
req, err = http.NewRequest(http.MethodGet, u, nil)
if err != nil {
Print("buildRequest: NewRequest: "+err.Error()+"\n", Red)
return
}
}
req.Header.Set("User-Agent", config.UserAgent)
/* add headers */
for _, h := range conf.Headers {
// hv is a Slice where hv[0] = header name and hv[1] = header value
hv := strings.SplitN(h, ":", 2)
if len(hv) != 2 {
msg := fmt.Sprintf("Could not split %s into header name and value\n", h)
PrintVerbose(msg, Yellow, 1)
continue
}
if strings.EqualFold(hv[0], "Host") {
msg := fmt.Sprintf("Overwriting Host:%s with Host:%s\n", req.URL.Host, hv[1])
PrintVerbose(msg, NoColor, 2)
req.Host = hv[1]
} else {
// check if header already exists
if val := req.Header.Get(hv[0]); val != "" {
msg := fmt.Sprintf("Overwriting %s:%s with %s\n", hv[0], val, hv[1])
PrintVerbose(msg, Red, 2)
}
// set header
req.Header.Set(hv[0], hv[1])
}
}
/***************/
/* add query parameters */
q := req.URL.Query()
for _, p := range conf.Parameters {
// pv is a Slice where pv[0] = parameter name and pv[1] = parameter value
pv := strings.SplitN(p, "=", 2)
if len(pv) != 2 {
msg := fmt.Sprintf("Could not split %s into parameter name and value\n", p)
PrintVerbose(msg, Yellow, 1)
continue
}
// check if parameter already exists
if val := req.URL.Query().Get(pv[0]); val != "" {
msg := fmt.Sprintf("Overwriting %s=%s with %s\n", pv[0], val, pv[1])
PrintVerbose(msg, NoColor, 2)
}
// set parameter
q.Set(pv[0], pv[1])
}
req.URL.RawQuery = q.Encode()
/***********************/
return
}
func setQuery(req *http.Request, key string, payload string) {
q := req.URL.Query()
q.Set(key, payload)
req.URL.RawQuery = q.Encode()
}
func setPost(req *http.Request, key string, payload string) (*http.Request, error) {
conf := config
bodyString := ""
for k, v := range postParams {
bodyString += k + "="
if k == key {
bodyString += url.QueryEscape(payload)
} else {
bodyString += v + "&"
}
}
bodyString = strings.TrimSuffix(bodyString, "&")
conf.Data = bodyString
return buildRequest(req.URL.String(), conf)
}
func setHeader(req *http.Request, key string, payload string) {
if key == "Host" { // Host header tests need a custom implementation as the net/http library doesn't allow host header manipulations
req.Host = payload + ".com"
} else {
req.Header.Set(key, payload)
}
}
// dialConnection establishes the appropriate connection based on the scheme (http or https)
func dialConnection(scheme, host string) (net.Conn, error) {
if scheme == "https" {
return tls.Dial("tcp", host, &tls.Config{RootCAs: caCertPool,
InsecureSkipVerify: true})
}
return net.Dial("tcp", host)
}
// doRequestInternal performs the actual HTTP request without following redirects
func doRequestInternal(req *http.Request) (resp *http.Response, err error) {
var conn net.Conn
err = limiter.Wait(context.Background())
if err != nil {
msg := "doRequest rate limiter error: " + err.Error() + "\n"
Print(msg, Red)
}
// if there are no host header manipulations, do a standard client.do
if req.Host == req.URL.Host || req.Host == req.URL.Hostname() {
// Create a client that doesn't automatically follow redirects
// but uses the same settings as the default client (cookie jar, timeout, etc.)
client := &http.Client{
Timeout: time.Duration(config.Timeout) * time.Second,
Jar: http.DefaultClient.Jar, // Use the same cookie jar
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Return an error to prevent automatic redirect following
return http.ErrUseLastResponse
},
}
// Copy transport settings from default transport
if defaultTransport, ok := http.DefaultTransport.(*http.Transport); ok {
transport := defaultTransport.Clone()
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} else {
transport.TLSClientConfig.InsecureSkipVerify = true
}
client.Transport = transport
} else {
// Fallback if DefaultTransport is not the expected type
client.Transport = http.DefaultTransport
if transport, ok := client.Transport.(*http.Transport); ok {
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} else {
transport.TLSClientConfig.InsecureSkipVerify = true
}
}
}
// Do Request
resp, err = client.Do(req)
if err != nil {
return
}
// if there is a host header manipulation, do the following in order to circumvent net/https limitations
} else {
// Determine if HTTP or HTTPS is used
scheme := "http"
if req.URL.Scheme == "https" {
scheme = "https"
if !strings.Contains(req.URL.Host, ":") {
req.URL.Host += ":443"
}
} else {
if !strings.Contains(req.URL.Host, ":") {
req.URL.Host += ":80"
}
}
// Construct the request line
requestLine := []byte(fmt.Sprintf("%s %s %s\r\n", req.Method, req.URL.String(), req.Proto))
// Read the request body, if present
var bodyBytes []byte
if req.Body != nil {
bodyBytes, err = io.ReadAll(req.Body)
if err != nil {
err = errors.New("Error reading request body: " + err.Error())
return
}
}
/* Add headers which golang doesn't add before sending the actual request */
if len(bodyBytes) > 0 {
req.Header.Set("Content-Length", strconv.Itoa(len(bodyBytes)))
}
req.Header.Set("Host", req.Host)
req.Header.Set("User-Agent", req.UserAgent())
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Construct the headers
var reqHeaders bytes.Buffer
req.Header.Write(&reqHeaders)
// Combine the request line, headers, and request body (if applicable)
requestData := append(append(append(requestLine, reqHeaders.Bytes()...), []byte("\r\n")...), bodyBytes...)
timeoutDuration := time.Duration(config.Timeout) * time.Second
if config.ProxyURL != "" {
var proxyURL *url.URL
var proxyConn net.Conn
// Connect to the proxy
proxyURL, err = url.Parse(config.ProxyURL) // Replace with your proxy URL
if err != nil {
err = errors.New("Error parsing proxy URL:" + err.Error())
return
}
proxyConn, err = dialConnection(proxyURL.Scheme, proxyURL.Host)
if err != nil {
err = errors.New("Error connecting to proxy:" + err.Error())
return
}
defer proxyConn.Close()
// Set a deadline for proxyConn
err = proxyConn.SetDeadline(time.Now().Add(timeoutDuration))
if err != nil {
err = errors.New("Error setting proxyConn deadline:" + err.Error())
return
}
// Send the request through the proxy
proxyRequest := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", req.URL.Host, req.URL.Host)
_, err = proxyConn.Write([]byte(proxyRequest))
if err != nil {
err = errors.New("Error sending CONNECT request to proxy: " + err.Error())
return
}
// Read the response from the proxy
var n int
proxyResponse := make([]byte, 4096)
n, err = proxyConn.Read(proxyResponse)
if err != nil {
err = errors.New("Error reading response from proxy: " + err.Error())
return
}
proxyResponseStr := string(proxyResponse[:n])
PrintVerbose("Proxy Response: "+proxyResponseStr+"\n", NoColor, 2)
// If its a http URL, the proxyconn can be used as is. Otherwise a TLS handshake needs to be established
if scheme == "http" {
conn = proxyConn
} else {
// Establish a TLS connection to the target server via the proxy
targetConn := tls.Client(proxyConn, &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: true, // Skip certificate verification (for demonstration purposes only)
ServerName: req.URL.Hostname(),
})
err = targetConn.Handshake()
if err != nil {
err = errors.New("Error establishing TLS connection to target server: " + err.Error())
return
}
conn = targetConn
}
} else {
// Send the request using net.Dial or tls.Dial
conn, err = dialConnection(scheme, req.URL.Host)
if err != nil {
err = errors.New("Error establishing connection: " + err.Error())
return
}
defer conn.Close()
}
err = conn.SetDeadline(time.Now().Add(timeoutDuration))
if err != nil {
err = errors.New("Error setting conn deadline:" + err.Error())
return
}
_, err = conn.Write(requestData)
if err != nil {
err = errors.New("Error sending request: " + err.Error())
return
}
// Read the response
responseData := make([]byte, 4096)
var n int
n, err = conn.Read(responseData)
if err != nil {
err = errors.New("Error reading response: " + err.Error())
return
}
response := string(responseData[:n])
// Parse the response
resp, err = http.ReadResponse(bufio.NewReader(bytes.NewReader([]byte(response))), req)
if err != nil {
err = errors.New("Error reading response from target server: " + err.Error())
return
}
}
return resp, err
}
func doRequest(req *http.Request) (body string, headers http.Header, status int, dump string, err error) {
return doRequestWithRedirect(req, 0)
}
// doRequestWithRedirect performs the HTTP request and follows redirects up to maxRedirects times
func doRequestWithRedirect(req *http.Request, redirectDepth int) (body string, headers http.Header, status int, dump string, err error) {
const maxRedirects = 10
// Perform the request
resp, err := doRequestInternal(req)
if err != nil {
return
}
defer resp.Body.Close()
// Check if response is a redirect
redirectStatuses := []int{301, 302, 303, 307, 308}
isRedirect := false
for _, status := range redirectStatuses {
if resp.StatusCode == status {
isRedirect = true
break
}
}
// Follow redirect if applicable
if isRedirect && redirectDepth < maxRedirects {
// Get the Location header
location := resp.Header.Get("Location")
if location != "" {
// Parse the redirect URL
redirectURL, parseErr := url.Parse(location)
if parseErr != nil {
PrintVerbose("Error parsing redirect URL: "+parseErr.Error()+"\n", Yellow, 1)
} else {
// Resolve relative URLs
if !redirectURL.IsAbs() {
redirectURL = req.URL.ResolveReference(redirectURL)
}
PrintVerbose("Following redirect from "+req.URL.String()+" to "+redirectURL.String()+"\n", Cyan, 1)
// Create a new request to the redirect location
// Use GET for 301, 302, 303 redirects (per HTTP spec)
// Preserve method for 307, 308 redirects
method := req.Method
if resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 303 {
method = "GET"
}
// Build new request
newReq, newReqErr := http.NewRequest(method, redirectURL.String(), nil)
if newReqErr != nil {
PrintVerbose("Error creating redirect request: "+newReqErr.Error()+"\n", Yellow, 1)
} else {
// Copy headers from original request (except Host)
for key, values := range req.Header {
if strings.EqualFold(key, "Host") {
continue
}
for _, value := range values {
newReq.Header.Add(key, value)
}
}
// Preserve Host header manipulation if present
if req.Host != req.URL.Host && req.Host != req.URL.Hostname() {
newReq.Host = req.Host
}
// Recursively follow the redirect
return doRequestWithRedirect(newReq, redirectDepth+1)
}
}
}
} else if isRedirect && redirectDepth >= maxRedirects {
PrintVerbose("Maximum redirect depth reached, stopping redirect following\n", Yellow, 1)
}
// Process the final response (or non-redirect response)
if boolReport {
var dumpBytes []byte
dumpBytes, err = httputil.DumpResponse(resp, true)
if err != nil {
// Sometimes DumpResponse throws an error like "http: ContentLength=54 with Body length 0". However the Content-Length Header and the body length are correct...
if !strings.Contains(err.Error(), "unexpected EOF") {
PrintVerbose("Error dumping response: "+err.Error()+"\n", Yellow, 1)
}
err = nil
dumpBytes, _ = httputil.DumpResponse(resp, false)
}
dump = string(dumpBytes)
}
// Read Response
var resBody []byte
resBody, err = io.ReadAll(resp.Body)
if err != nil {
if strings.Contains(err.Error(), "unexpected EOF") {
err = nil
} else {
err = errors.New("Error reading response body: " + err.Error())
return
}
}
body = string(resBody)
headers = resp.Header
status = resp.StatusCode
return
}