diff --git a/v2/pkg/protocols/http/raw/doc.go b/v2/pkg/protocols/http/raw/doc.go new file mode 100644 index 00000000..5a65d1a4 --- /dev/null +++ b/v2/pkg/protocols/http/raw/doc.go @@ -0,0 +1,2 @@ +// Package raw provides raw http request parsing abilities for nuclei. +package raw diff --git a/v2/pkg/protocols/http/raw/raw.go b/v2/pkg/protocols/http/raw/raw.go new file mode 100644 index 00000000..894c61f2 --- /dev/null +++ b/v2/pkg/protocols/http/raw/raw.go @@ -0,0 +1,118 @@ +package raw + +import ( + "bufio" + "fmt" + "io/ioutil" + "net/url" + "strings" +) + +// Request defines a HTTP raw request structure +type Request struct { + Method string + Path string + Data string + Headers map[string]string +} + +// Parse parses the raw request as supplied by the user +func Parse(request string, unsafe bool) (*Request, error) { + reader := bufio.NewReader(strings.NewReader(request)) + + rawRequest := Request{ + Headers: make(map[string]string), + } + + s, err := reader.ReadString('\n') + if err != nil { + return nil, fmt.Errorf("could not read request: %s", err) + } + + parts := strings.Split(s, " ") + + //nolint:gomnd // this is not a magic number + if len(parts) < 3 { + return nil, fmt.Errorf("malformed request supplied") + } + // Set the request Method + rawRequest.Method = parts[0] + + // Accepts all malformed headers + var key, value string + for { + line, readErr := reader.ReadString('\n') + line = strings.TrimSpace(line) + + if readErr != nil || line == "" { + break + } + + //nolint:gomnd // this is not a magic number + p := strings.SplitN(line, ":", 2) + key = p[0] + if len(p) > 1 { + value = p[1] + } + + // in case of unsafe requests multiple headers should be accepted + // therefore use the full line as key + _, found := rawRequest.Headers[key] + if unsafe && found { + rawRequest.Headers[line] = "" + } else { + rawRequest.Headers[key] = value + } + } + + // Handle case with the full http url in path. In that case, + // ignore any host header that we encounter and use the path as request URL + if !unsafe && strings.HasPrefix(parts[1], "http") { + parsed, parseErr := url.Parse(parts[1]) + if parseErr != nil { + return nil, fmt.Errorf("could not parse request URL: %s", parseErr) + } + + rawRequest.Path = parts[1] + rawRequest.Headers["Host"] = parsed.Host + } else { + rawRequest.Path = parts[1] + } + + // Set the request body + b, err := ioutil.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("could not read request body: %s", err) + } + rawRequest.Data = string(b) + return &rawRequest, nil +} + +// URL returns the full URL for a raw request based on provided metadata +func (r *Request) URL(BaseURL string) (string, error) { + parsed, err := url.Parse(BaseURL) + if err != nil { + return "", err + } + + var hostURL string + if r.Headers["Host"] == "" { + hostURL = parsed.Host + } else { + hostURL = r.Headers["Host"] + } + + if r.Path == "" { + r.Path = parsed.Path + } else if strings.HasPrefix(r.Path, "?") { + r.Path = fmt.Sprintf("%s%s", parsed.Path, r.Path) + } + + builder := &strings.Builder{} + builder.WriteString(parsed.Scheme) + builder.WriteString("://") + builder.WriteString(strings.TrimSpace(hostURL)) + builder.WriteString(r.Path) + URL := builder.String() + return URL, nil +} diff --git a/v2/pkg/protocols/http/raw/raw_test.go b/v2/pkg/protocols/http/raw/raw_test.go new file mode 100644 index 00000000..0b00c0c4 --- /dev/null +++ b/v2/pkg/protocols/http/raw/raw_test.go @@ -0,0 +1,28 @@ +package raw + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseRawRequest(t *testing.T) { + request, err := Parse(`GET /manager/html HTTP/1.1 +Host: {{Hostname}} +Authorization: Basic {{base64('username:password')}} +User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0 +Accept-Language: en-US,en;q=0.9 +Connection: close`, true) + require.Nil(t, err, "could not parse GET request") + require.Equal(t, "GET", request.Method, "Could not parse GET method request correctly") + require.Equal(t, "/manager/html", request.Path, "Could not parse request path correctly") + + request, err = Parse(`POST /login HTTP/1.1 +Host: {{Hostname}} +Connection: close + +username=admin&password=login`, true) + require.Nil(t, err, "could not parse POST request") + require.Equal(t, "POST", request.Method, "Could not parse POST method request correctly") + require.Equal(t, "username=admin&password=login", request.Data, "Could not parse request data correctly") +}