-
Notifications
You must be signed in to change notification settings - Fork 38
Description
This proposal is to replace the very large (and growing) pile of options for protocol
with a functional options pattern. We currently have 8 variants of the the HTTPSendAndRecv
function in protocol, the longest of which has the comically large function signature of HTTPSendAndRecvWithHeadersNoRedirect
. This is likely to grow and feels partially unorganized in the root protocol
package which has no separation of protocols, and the only separation is inside of the sub-packages.
Instead of growing the protocols I propose that we instead provide a protocol.HTTP
type and a func NewRequest(verb string, url string, payload string, opts ...Option) (*http.Response, string, bool)
signature that allows for dynamic options. The theory is that instead of the ubiquitous:
resp, body, ok := protocol.HTTPSendAndRecvWithHeadersNoRedirect("GET", url, "", headers)
You would call:
resp, body, ok := protocol.HTTP.NewRequest("GET", url, "",
protocol.WithHeaders(headers),
protocol.WithoutRedirect())
or without a data parameter have default empty and
resp, body, ok := protocol.HTTP.NewRequest("GET", url,
protocol.WithData("{}"),
protocol.WithHeaders(headers),
protocol.WithoutRedirect())
The With
and Without
components could also be removed and shorten the options to Headers
and Redirect(bool)
signatures.
This does increase verbosity a bit, but does allow for quick modifications to a single request function without having to continue to increase the amount of signatures. The flexibility of having a subset of options also has a few benefits:
- It also could allow us to do interesting things like introduce default hooks to the calls so that you could add modifying functions to the requests.
- Options can be added without breaking API easier than full functions
- Other protocols could be merged back into
protocol
with another type and struct (could be seen as a positive or negative) - Lower level additions could be made, such as with options to support uTLS handshakes that could be customized or match our
GlobalUA
Here is a bit of a incomplete mock of this that I was doing inside of protocol/http
prior to discussions of a v2
:
package http
import (
"crypto/tls"
"io"
"net"
"net/http"
nethttp "net/http"
"net/url"
"strings"
"time"
"github.com/vulncheck-oss/go-exploit/output"
"github.com/vulncheck-oss/go-exploit/transform"
)
// GlobalUA is the default User-Agent for all go-exploit comms
//
//go:embed http-user-agent.txt
var GlobalUA string
// GlobalCommTimeout is the default timeout for all socket communications.
var GlobalCommTimeout = 10
type options struct {
redirect bool
cache bool
urlEncodeParameters bool
redirectDepth *int
parameters *map[string]string
headers *map[string]string
basicAuth *string
useragent *string
payload *string
client *nethttp.Client
cookies []*nethttp.Cookie
}
type Option func(options *options) bool
func WithoutRedirect() Option {
return func(options *options) bool {
options.redirect = false
return true
}
}
func WithParameters(params map[string]string, urlEncode bool) Option {
return func(options *options) bool {
paramsCopy := make(map[string]string)
if urlEncode {
for k, v := range params {
paramsCopy[k] = url.QueryEscape(v)
}
} else {
paramsCopy = params
}
options.parameters = ¶msCopy
return true
}
}
func WithData(data string) Option {
return func(option *options) bool {
option.payload = &data
return true
}
}
func WithBasicAuth(username, password string) Option {
return func(option *options) bool {
b := "Basic " + transform.EncodeBase64(username+":"+password)
option.basicAuth = &b
return true
}
}
func WithHeaders(params map[string]string) Option {
return func(option *options) bool {
option.headers = ¶ms
return true
}
}
func NewRequest(verb string, url string, payload string, opts ...Option) (*nethttp.Response, string, bool) {
var options options
// set defaults
options.redirect = true
options.cache = false
options.urlEncodeParameters = false
options.useragent = &GlobalUA
// options.client = &nethttp.Client{}
for _, opt := range opts {
ok := opt(&options)
if !ok {
return &nethttp.Response{}, "", false
}
}
if options.client == nil {
if !options.redirect {
options.client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: time.Duration(GlobalCommTimeout) * time.Second,
}).Dial,
TLSClientConfig: (&tls.Config{
InsecureSkipVerify: true,
// We have no control over the SSL versions supported on the remote target. Be permissive for more targets.
MinVersion: tls.VersionSSL30,
}),
},
Timeout: time.Duration(GlobalCommTimeout) * time.Second,
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
},
}
} else {
options.client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: time.Duration(GlobalCommTimeout) * time.Second,
}).Dial,
TLSClientConfig: (&tls.Config{
InsecureSkipVerify: true,
// We have no control over the SSL versions supported on the remote target. Be permissive for more targets.
MinVersion: tls.VersionSSL30,
}),
},
Timeout: time.Duration(GlobalCommTimeout) * time.Second,
}
}
}
req, err := http.NewRequest(verb, url, strings.NewReader(payload))
if err != nil {
output.PrintfFrameworkError("HTTP request creation error: %s", err)
return nil, "", false
}
if options.headers != nil {
for key, value := range *options.headers {
if key == "Host" {
// host can't be set directly
req.Host = value
} else {
// don't use the Set function because the module might modify key. Set the header directly.
req.Header[key] = []string{value}
}
}
}
// set headers on the request
req.Header.Set("User-Agent", *options.useragent)
if !options.redirect {
// ignore the redirect
options.client.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
}
}
resp, err := options.client.Do(req)
if err != nil {
output.PrintfFrameworkError("HTTP request error: %s", err)
return resp, "", false
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
return resp, string(bodyBytes), true
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status