Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix missing browser init #5896

Merged
merged 8 commits into from
Dec 17, 2024
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,
coderabbitai[bot] marked this conversation as resolved.
Show resolved Hide resolved
}
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
}
coderabbitai[bot] marked this conversation as resolved.
Show resolved Hide resolved

// 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)
coderabbitai[bot] marked this conversation as resolved.
Show resolved Hide resolved

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