mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 19:15:25 +00:00
fix missing browser init (#5896)
* fix missing browser init * . * using lazy init * updating test with new web ui * go mod * sandbox test * non fatal error
This commit is contained in:
parent
cf334e55c7
commit
1e87ca82c8
2
go.mod
2
go.mod
@ -277,7 +277,7 @@ require (
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
|
|||||||
@ -40,6 +40,7 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
|||||||
Colorizer: aurora.NewAurora(true),
|
Colorizer: aurora.NewAurora(true),
|
||||||
ResumeCfg: types.NewResumeCfg(),
|
ResumeCfg: types.NewResumeCfg(),
|
||||||
Parser: base.parser,
|
Parser: base.parser,
|
||||||
|
Browser: base.browserInstance,
|
||||||
}
|
}
|
||||||
if opts.RateLimitMinute > 0 {
|
if opts.RateLimitMinute > 0 {
|
||||||
opts.RateLimit = opts.RateLimitMinute
|
opts.RateLimit = opts.RateLimitMinute
|
||||||
|
|||||||
@ -393,7 +393,9 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string
|
|||||||
if existingTemplatePath, found := templateIDPathMap[template.ID]; !found {
|
if existingTemplatePath, found := templateIDPathMap[template.ID]; !found {
|
||||||
templateIDPathMap[template.ID] = templatePath
|
templateIDPathMap[template.ID] = templatePath
|
||||||
} else {
|
} else {
|
||||||
areTemplatesValid = false
|
// TODO: until https://github.com/projectdiscovery/nuclei-templates/issues/11324 is deployed
|
||||||
|
// disable strict validation to allow GH actions to run
|
||||||
|
// areTemplatesValid = false
|
||||||
gologger.Warning().Msgf("Found duplicate template ID during validation '%s' => '%s': %s\n", templatePath, existingTemplatePath, template.ID)
|
gologger.Warning().Msgf("Found duplicate template ID during validation '%s' => '%s': %s\n", templatePath, existingTemplatePath, template.ID)
|
||||||
}
|
}
|
||||||
if !isWorkflow && len(template.Workflows) > 0 {
|
if !isWorkflow && len(template.Workflows) > 0 {
|
||||||
|
|||||||
@ -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)")),
|
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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-rod/rod"
|
"github.com/go-rod/rod"
|
||||||
"github.com/go-rod/rod/lib/launcher"
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
@ -23,8 +24,10 @@ type Browser struct {
|
|||||||
tempDir string
|
tempDir string
|
||||||
previousPIDs map[int32]struct{} // track already running PIDs
|
previousPIDs map[int32]struct{} // track already running PIDs
|
||||||
engine *rod.Browser
|
engine *rod.Browser
|
||||||
httpclient *http.Client
|
|
||||||
options *types.Options
|
options *types.Options
|
||||||
|
// use getHTTPClient to get the http client
|
||||||
|
httpClient *http.Client
|
||||||
|
httpClientOnce *sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new nuclei headless browser module
|
// New creates a new nuclei headless browser module
|
||||||
@ -101,17 +104,12 @@ func New(options *types.Options) (*Browser, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpclient, err := newHttpClient(options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := &Browser{
|
engine := &Browser{
|
||||||
tempDir: dataStore,
|
tempDir: dataStore,
|
||||||
customAgent: customAgent,
|
customAgent: customAgent,
|
||||||
engine: browser,
|
engine: browser,
|
||||||
httpclient: httpclient,
|
options: options,
|
||||||
options: options,
|
httpClientOnce: &sync.Once{},
|
||||||
}
|
}
|
||||||
engine.previousPIDs = previousPIDs
|
engine.previousPIDs = previousPIDs
|
||||||
return engine, nil
|
return engine, nil
|
||||||
@ -121,7 +119,7 @@ func New(options *types.Options) (*Browser, error) {
|
|||||||
func MustDisableSandbox() bool {
|
func MustDisableSandbox() bool {
|
||||||
// linux with root user needs "--no-sandbox" option
|
// linux with root user needs "--no-sandbox" option
|
||||||
// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
|
// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
|
||||||
return osutils.IsLinux() && os.Geteuid() == 0
|
return osutils.IsLinux()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserAgent sets custom user agent to the browser
|
// SetUserAgent sets custom user agent to the browser
|
||||||
@ -134,6 +132,14 @@ func (b *Browser) UserAgent() string {
|
|||||||
return b.customAgent
|
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
|
// Close closes the browser engine
|
||||||
func (b *Browser) Close() {
|
func (b *Browser) Close() {
|
||||||
b.engine.Close()
|
b.engine.Close()
|
||||||
|
|||||||
@ -67,10 +67,15 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
payloads: payloads,
|
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
|
// in case the page has request/response modification rules - enable global hijacking
|
||||||
if createdPage.hasModificationRules() || containsModificationActions(actions...) {
|
if createdPage.hasModificationRules() || containsModificationActions(actions...) {
|
||||||
hijackRouter := page.HijackRequests()
|
hijackRouter := page.HijackRequests()
|
||||||
if err := hijackRouter.Add("*", "", createdPage.routingRuleHandler); err != nil {
|
if err := hijackRouter.Add("*", "", createdPage.routingRuleHandler(httpclient)); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
createdPage.hijackRouter = hijackRouter
|
createdPage.hijackRouter = hijackRouter
|
||||||
|
|||||||
@ -649,7 +649,10 @@ func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handle
|
|||||||
|
|
||||||
_ = protocolstate.Init(opts)
|
_ = protocolstate.Init(opts)
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false, UseInstalledChrome: testheadless.HeadlessLocal})
|
browser, err := New(&types.Options{
|
||||||
|
ShowBrowser: false,
|
||||||
|
UseInstalledChrome: testheadless.HeadlessLocal,
|
||||||
|
})
|
||||||
require.Nil(t, err, "could not create browser")
|
require.Nil(t, err, "could not create browser")
|
||||||
defer browser.Close()
|
defer browser.Close()
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -11,95 +12,97 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// routingRuleHandler handles proxy rule for actions related to request/response modification
|
// routingRuleHandler handles proxy rule for actions related to request/response modification
|
||||||
func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
|
func (p *Page) routingRuleHandler(httpClient *http.Client) func(ctx *rod.Hijack) {
|
||||||
// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
|
return func(ctx *rod.Hijack) {
|
||||||
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
|
// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
|
||||||
for _, rule := range p.rules {
|
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
|
||||||
if rule.Part != "request" {
|
for _, rule := range p.rules {
|
||||||
continue
|
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
|
// each http request is performed via the native go http client
|
||||||
// we first inject the shared cookies
|
// we first inject the shared cookies
|
||||||
if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
if !p.options.DisableCookie {
|
||||||
p.instance.browser.httpclient.Jar.SetCookies(ctx.Request.URL(), cookies)
|
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range p.rules {
|
|
||||||
if rule.Part != "response" {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rule.Action {
|
// perform the request
|
||||||
case ActionAddHeader:
|
_ = ctx.LoadResponse(httpClient, true)
|
||||||
ctx.Response.Headers().Add(rule.Args["key"], rule.Args["value"])
|
|
||||||
case ActionSetHeader:
|
if !p.options.DisableCookie {
|
||||||
ctx.Response.Headers().Set(rule.Args["key"], rule.Args["value"])
|
// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
|
||||||
case ActionDeleteHeader:
|
// keeps existing one if not present
|
||||||
ctx.Response.Headers().Del(rule.Args["key"])
|
if cookies := httpClient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
||||||
case ActionSetBody:
|
p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies)
|
||||||
body := rule.Args["body"]
|
}
|
||||||
ctx.Response.Headers().Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
|
||||||
ctx.Response.SetBody(rule.Args["body"])
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// store history
|
for _, rule := range p.rules {
|
||||||
req := ctx.Request.Req()
|
if rule.Part != "response" {
|
||||||
var rawReq string
|
continue
|
||||||
if raw, err := httputil.DumpRequestOut(req, true); err == nil {
|
}
|
||||||
rawReq = string(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempts to rebuild the response
|
switch rule.Action {
|
||||||
var rawResp strings.Builder
|
case ActionAddHeader:
|
||||||
respPayloads := ctx.Response.Payload()
|
ctx.Response.Headers().Add(rule.Args["key"], rule.Args["value"])
|
||||||
if respPayloads != nil {
|
case ActionSetHeader:
|
||||||
rawResp.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\n", respPayloads.ResponseCode, respPayloads.ResponsePhrase))
|
ctx.Response.Headers().Set(rule.Args["key"], rule.Args["value"])
|
||||||
for _, header := range respPayloads.ResponseHeaders {
|
case ActionDeleteHeader:
|
||||||
rawResp.WriteString(header.Name + ": " + header.Value + "\n")
|
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"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rawResp.WriteString("\n")
|
|
||||||
rawResp.WriteString(ctx.Response.Body())
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump request
|
// store history
|
||||||
historyData := HistoryData{
|
req := ctx.Request.Req()
|
||||||
RawRequest: rawReq,
|
var rawReq string
|
||||||
RawResponse: rawResp.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")
|
||||||
|
}
|
||||||
|
rawResp.WriteString("\n")
|
||||||
|
rawResp.WriteString(ctx.Response.Body())
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump request
|
||||||
|
historyData := HistoryData{
|
||||||
|
RawRequest: rawReq,
|
||||||
|
RawResponse: rawResp.String(),
|
||||||
|
}
|
||||||
|
p.addToHistory(historyData)
|
||||||
}
|
}
|
||||||
p.addToHistory(historyData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// routingRuleHandlerNative handles native proxy rule
|
// routingRuleHandlerNative handles native proxy rule
|
||||||
|
|||||||
@ -20,7 +20,7 @@ http:
|
|||||||
matchers:
|
matchers:
|
||||||
- type: dsl
|
- type: dsl
|
||||||
dsl:
|
dsl:
|
||||||
- contains(http_body, 'ProjectDiscovery Cloud Platform') # check for http string
|
- contains(http_body, 'ProjectDiscovery') # check for http string
|
||||||
- dns_cname == 'cname.vercel-dns.com' # check for cname (extracted information from dns response)
|
- dns_cname == 'cname.vercel-dns.com' # check for cname (extracted information from dns response)
|
||||||
- ssl_subject_cn == 'cloud.projectdiscovery.io'
|
- ssl_subject_cn == 'cloud.projectdiscovery.io'
|
||||||
condition: and
|
condition: and
|
||||||
Loading…
x
Reference in New Issue
Block a user