Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
Colorizer: aurora.NewAurora(true),
ResumeCfg: types.NewResumeCfg(),
Parser: base.parser,
Browser: base.browserInstance,
}
if opts.RateLimitMinute > 0 {
opts.RateLimit = opts.RateLimitMinute
Expand Down
2 changes: 1 addition & 1 deletion pkg/output/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestStandardWriterRequest(t *testing.T) {
fmt.Errorf("GET https://example.com/tcpconfig.html/tcpconfig.html giving up after 2 attempts: %w", errors.New("context deadline exceeded (Client.Timeout exceeded while awaiting headers)")),
)

require.Equal(t, `{"template":"misconfiguration/tcpconfig.yaml","type":"http","input":"https://example.com/tcpconfig.html","address":"example.com:443","error":"context deadline exceeded (Client.Timeout exceeded while awaiting headers)","kind":"unknown-error"}`, errorWriter.String())
require.Equal(t, `{"template":"misconfiguration/tcpconfig.yaml","type":"http","input":"https://example.com/tcpconfig.html","address":"example.com:443","error":"cause=\"context deadline exceeded (Client.Timeout exceeded while awaiting headers)\"","kind":"unknown-error"}`, errorWriter.String())
})
}

Expand Down
28 changes: 17 additions & 11 deletions pkg/protocols/headless/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"os"
"strings"
"sync"

"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
Expand All @@ -23,8 +24,10 @@ type Browser struct {
tempDir string
previousPIDs map[int32]struct{} // track already running PIDs
engine *rod.Browser
httpclient *http.Client
options *types.Options
// use getHTTPClient to get the http client
httpClient *http.Client
httpClientOnce *sync.Once
}

// New creates a new nuclei headless browser module
Expand Down Expand Up @@ -101,17 +104,12 @@ func New(options *types.Options) (*Browser, error) {
}
}

httpclient, err := newHttpClient(options)
if err != nil {
return nil, err
}

engine := &Browser{
tempDir: dataStore,
customAgent: customAgent,
engine: browser,
httpclient: httpclient,
options: options,
tempDir: dataStore,
customAgent: customAgent,
engine: browser,
options: options,
httpClientOnce: &sync.Once{},
}
engine.previousPIDs = previousPIDs
return engine, nil
Expand All @@ -134,6 +132,14 @@ func (b *Browser) UserAgent() string {
return b.customAgent
}

func (b *Browser) getHTTPClient() (*http.Client, error) {
var err error
b.httpClientOnce.Do(func() {
b.httpClient, err = newHttpClient(b.options)
})
return b.httpClient, err
}

// Close closes the browser engine
func (b *Browser) Close() {
b.engine.Close()
Expand Down
7 changes: 6 additions & 1 deletion pkg/protocols/headless/engine/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
payloads: payloads,
}

httpclient, err := i.browser.getHTTPClient()
if err != nil {
return nil, nil, err
}

// in case the page has request/response modification rules - enable global hijacking
if createdPage.hasModificationRules() || containsModificationActions(actions...) {
hijackRouter := page.HijackRequests()
if err := hijackRouter.Add("*", "", createdPage.routingRuleHandler); err != nil {
if err := hijackRouter.Add("*", "", createdPage.routingRuleHandler(httpclient)); err != nil {
return nil, nil, err
}
createdPage.hijackRouter = hijackRouter
Expand Down
149 changes: 76 additions & 73 deletions pkg/protocols/headless/engine/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package engine

import (
"fmt"
"net/http"
"net/http/httputil"
"strings"

Expand All @@ -11,95 +12,97 @@ import (
)

// routingRuleHandler handles proxy rule for actions related to request/response modification
func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
for _, rule := range p.rules {
if rule.Part != "request" {
continue
func (p *Page) routingRuleHandler(httpClient *http.Client) func(ctx *rod.Hijack) {
return func(ctx *rod.Hijack) {
// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
for _, rule := range p.rules {
if rule.Part != "request" {
continue
}

switch rule.Action {
case ActionSetMethod:
rule.Do(func() {
ctx.Request.Req().Method = rule.Args["method"]
})
case ActionAddHeader:
ctx.Request.Req().Header.Add(rule.Args["key"], rule.Args["value"])
case ActionSetHeader:
ctx.Request.Req().Header.Set(rule.Args["key"], rule.Args["value"])
case ActionDeleteHeader:
ctx.Request.Req().Header.Del(rule.Args["key"])
case ActionSetBody:
body := rule.Args["body"]
ctx.Request.Req().ContentLength = int64(len(body))
ctx.Request.SetBody(body)
}
}

switch rule.Action {
case ActionSetMethod:
rule.Do(func() {
ctx.Request.Req().Method = rule.Args["method"]
})
case ActionAddHeader:
ctx.Request.Req().Header.Add(rule.Args["key"], rule.Args["value"])
case ActionSetHeader:
ctx.Request.Req().Header.Set(rule.Args["key"], rule.Args["value"])
case ActionDeleteHeader:
ctx.Request.Req().Header.Del(rule.Args["key"])
case ActionSetBody:
body := rule.Args["body"]
ctx.Request.Req().ContentLength = int64(len(body))
ctx.Request.SetBody(body)
}
}

if !p.options.DisableCookie {
// each http request is performed via the native go http client
// we first inject the shared cookies
if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
p.instance.browser.httpclient.Jar.SetCookies(ctx.Request.URL(), cookies)
if !p.options.DisableCookie {
if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
httpClient.Jar.SetCookies(ctx.Request.URL(), cookies)
}
}
}

// perform the request
_ = ctx.LoadResponse(p.instance.browser.httpclient, true)
// perform the request
_ = ctx.LoadResponse(httpClient, true)

if !p.options.DisableCookie {
// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
// keeps existing one if not present
if cookies := p.instance.browser.httpclient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies)
if !p.options.DisableCookie {
// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
// keeps existing one if not present
if cookies := httpClient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies)
}
}
}

for _, rule := range p.rules {
if rule.Part != "response" {
continue
for _, rule := range p.rules {
if rule.Part != "response" {
continue
}

switch rule.Action {
case ActionAddHeader:
ctx.Response.Headers().Add(rule.Args["key"], rule.Args["value"])
case ActionSetHeader:
ctx.Response.Headers().Set(rule.Args["key"], rule.Args["value"])
case ActionDeleteHeader:
ctx.Response.Headers().Del(rule.Args["key"])
case ActionSetBody:
body := rule.Args["body"]
ctx.Response.Headers().Set("Content-Length", fmt.Sprintf("%d", len(body)))
ctx.Response.SetBody(rule.Args["body"])
}
}

switch rule.Action {
case ActionAddHeader:
ctx.Response.Headers().Add(rule.Args["key"], rule.Args["value"])
case ActionSetHeader:
ctx.Response.Headers().Set(rule.Args["key"], rule.Args["value"])
case ActionDeleteHeader:
ctx.Response.Headers().Del(rule.Args["key"])
case ActionSetBody:
body := rule.Args["body"]
ctx.Response.Headers().Set("Content-Length", fmt.Sprintf("%d", len(body)))
ctx.Response.SetBody(rule.Args["body"])
// store history
req := ctx.Request.Req()
var rawReq string
if raw, err := httputil.DumpRequestOut(req, true); err == nil {
rawReq = string(raw)
}
}

// store history
req := ctx.Request.Req()
var rawReq string
if raw, err := httputil.DumpRequestOut(req, true); err == nil {
rawReq = string(raw)
}

// attempts to rebuild the response
var rawResp strings.Builder
respPayloads := ctx.Response.Payload()
if respPayloads != nil {
rawResp.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\n", respPayloads.ResponseCode, respPayloads.ResponsePhrase))
for _, header := range respPayloads.ResponseHeaders {
rawResp.WriteString(header.Name + ": " + header.Value + "\n")
// attempts to rebuild the response
var rawResp strings.Builder
respPayloads := ctx.Response.Payload()
if respPayloads != nil {
rawResp.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\n", respPayloads.ResponseCode, respPayloads.ResponsePhrase))
for _, header := range respPayloads.ResponseHeaders {
rawResp.WriteString(header.Name + ": " + header.Value + "\n")
}
rawResp.WriteString("\n")
rawResp.WriteString(ctx.Response.Body())
}
rawResp.WriteString("\n")
rawResp.WriteString(ctx.Response.Body())
}

// dump request
historyData := HistoryData{
RawRequest: rawReq,
RawResponse: rawResp.String(),
// dump request
historyData := HistoryData{
RawRequest: rawReq,
RawResponse: rawResp.String(),
}
p.addToHistory(historyData)
}
p.addToHistory(historyData)
}

// routingRuleHandlerNative handles native proxy rule
Expand Down
Loading