mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 13:45:28 +00:00
feat(headless): eval DSL exprs in args (#6017)
* refactor(headless): mv `input` -> `ctx` field name Signed-off-by: Dwi Siswanto <git@dw1.io> * feat(headless): eval DSL exprs in args Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(headless): rm duplicate imports Signed-off-by: Dwi Siswanto <git@dw1.io> * feat(headless): rm duplicate dumped req vars * refactor(headless): unify `getTimeParameter` retrieval Now, `getTimeParameter` tries to get the parameter as an integer, then as a `time.Duration`, and finally falls back to the default value (multiplied by the unit). Signed-off-by: Dwi Siswanto <git@dw1.io> * feat(headless): adjust default timeout value to 5s Signed-off-by: Dwi Siswanto <git@dw1.io> * refactor(headless): use `getTimeParameter` Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(headless): add nolint directive - `replaceWithValues` Signed-off-by: Dwi Siswanto <git@dw1.io> * feat(headless): revert parameter automerge & adds `inputURL` field Signed-off-by: Dwi Siswanto <git@dw1.io> * test(headless): add headless-dsl integration test Signed-off-by: Dwi Siswanto <git@dw1.io> --------- Signed-off-by: Dwi Siswanto <git@dw1.io>
This commit is contained in:
parent
d2d5ee9d48
commit
d2636b9ca2
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
var headlessTestcases = []TestCaseInfo{
|
var headlessTestcases = []TestCaseInfo{
|
||||||
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
|
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
|
||||||
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
|
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
|
||||||
|
{Path: "protocols/headless/headless-dsl.yaml", TestCase: &headlessBasic{}},
|
||||||
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
|
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
|
||||||
{Path: "protocols/headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}},
|
{Path: "protocols/headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}},
|
||||||
{Path: "protocols/headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}},
|
{Path: "protocols/headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}},
|
||||||
@ -30,7 +32,7 @@ type headlessBasic struct{}
|
|||||||
func (h *headlessBasic) Execute(filePath string) error {
|
func (h *headlessBasic) Execute(filePath string) error {
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
_, _ = w.Write([]byte("<html><body></body></html>"))
|
_, _ = fmt.Fprintf(w, "<html><body>%s</body></html>", r.URL.Query().Get("_"))
|
||||||
})
|
})
|
||||||
ts := httptest.NewServer(router)
|
ts := httptest.NewServer(router)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|||||||
18
integration_tests/protocols/headless/headless-dsl.yaml
Normal file
18
integration_tests/protocols/headless/headless-dsl.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
id: headless-dsl
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: Headless DSL
|
||||||
|
author: dwisiswant0
|
||||||
|
severity: info
|
||||||
|
tags: headless
|
||||||
|
|
||||||
|
headless:
|
||||||
|
- steps:
|
||||||
|
- action: navigate
|
||||||
|
args:
|
||||||
|
url: "{{BaseURL}}/?_={{urlencode(concat('foo', '-', 'bar'))}}"
|
||||||
|
- action: waitload
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- "foo-bar"
|
||||||
@ -11,14 +11,21 @@ import (
|
|||||||
|
|
||||||
"github.com/go-rod/rod"
|
"github.com/go-rod/rod"
|
||||||
"github.com/go-rod/rod/lib/proto"
|
"github.com/go-rod/rod/lib/proto"
|
||||||
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
||||||
|
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||||
|
errorutil "github.com/projectdiscovery/utils/errors"
|
||||||
|
urlutil "github.com/projectdiscovery/utils/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Page is a single page in an isolated browser instance
|
// Page is a single page in an isolated browser instance
|
||||||
type Page struct {
|
type Page struct {
|
||||||
input *contextargs.Context
|
ctx *contextargs.Context
|
||||||
|
inputURL *urlutil.URL
|
||||||
options *Options
|
options *Options
|
||||||
page *rod.Page
|
page *rod.Page
|
||||||
rules []rule
|
rules []rule
|
||||||
@ -29,6 +36,7 @@ type Page struct {
|
|||||||
History []HistoryData
|
History []HistoryData
|
||||||
InteractshURLs []string
|
InteractshURLs []string
|
||||||
payloads map[string]interface{}
|
payloads map[string]interface{}
|
||||||
|
variables map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryData contains the page request/response pairs
|
// HistoryData contains the page request/response pairs
|
||||||
@ -45,7 +53,7 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run runs a list of actions by creating a new page in the browser.
|
// Run runs a list of actions by creating a new page in the browser.
|
||||||
func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (ActionData, *Page, error) {
|
func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (ActionData, *Page, error) {
|
||||||
page, err := i.engine.Page(proto.TargetCreateTarget{})
|
page, err := i.engine.Page(proto.TargetCreateTarget{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -58,13 +66,33 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payloads = generators.MergeMaps(payloads,
|
||||||
|
generators.BuildPayloadFromOptions(i.browser.options),
|
||||||
|
)
|
||||||
|
|
||||||
|
target := ctx.MetaInput.Input
|
||||||
|
input, err := urlutil.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errorutil.NewWithErr(err).Msgf("could not parse URL %s", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasTrailingSlash := httputil.HasTrailingSlash(target)
|
||||||
|
variables := utils.GenerateVariables(input, hasTrailingSlash, contextargs.GenerateVariables(ctx))
|
||||||
|
variables = generators.MergeMaps(variables, payloads)
|
||||||
|
|
||||||
|
if vardump.EnableVarDump {
|
||||||
|
gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(variables))
|
||||||
|
}
|
||||||
|
|
||||||
createdPage := &Page{
|
createdPage := &Page{
|
||||||
options: options,
|
options: options,
|
||||||
page: page,
|
page: page,
|
||||||
input: input,
|
ctx: ctx,
|
||||||
instance: i,
|
instance: i,
|
||||||
mutex: &sync.RWMutex{},
|
mutex: &sync.RWMutex{},
|
||||||
payloads: payloads,
|
payloads: payloads,
|
||||||
|
variables: variables,
|
||||||
|
inputURL: input,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpclient, err := i.browser.getHTTPClient()
|
httpclient, err := i.browser.getHTTPClient()
|
||||||
@ -108,13 +136,13 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
// inject cookies
|
// inject cookies
|
||||||
// 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
|
||||||
URL, err := url.Parse(input.MetaInput.Input)
|
URL, err := url.Parse(ctx.MetaInput.Input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.DisableCookie {
|
if !options.DisableCookie {
|
||||||
if cookies := input.CookieJar.Cookies(URL); len(cookies) > 0 {
|
if cookies := ctx.CookieJar.Cookies(URL); len(cookies) > 0 {
|
||||||
var NetworkCookies []*proto.NetworkCookie
|
var NetworkCookies []*proto.NetworkCookie
|
||||||
for _, cookie := range cookies {
|
for _, cookie := range cookies {
|
||||||
networkCookie := &proto.NetworkCookie{
|
networkCookie := &proto.NetworkCookie{
|
||||||
@ -132,7 +160,7 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
}
|
}
|
||||||
params := proto.CookiesToParams(NetworkCookies)
|
params := proto.CookiesToParams(NetworkCookies)
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
param.URL = input.MetaInput.Input
|
param.URL = ctx.MetaInput.Input
|
||||||
}
|
}
|
||||||
err := page.SetCookies(params)
|
err := page.SetCookies(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,7 +169,7 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := createdPage.ExecuteActions(input, actions, payloads)
|
data, err := createdPage.ExecuteActions(ctx, actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -161,7 +189,7 @@ func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads m
|
|||||||
}
|
}
|
||||||
httpCookies = append(httpCookies, httpCookie)
|
httpCookies = append(httpCookies, httpCookie)
|
||||||
}
|
}
|
||||||
input.CookieJar.SetCookies(URL, httpCookies)
|
ctx.CookieJar.SetCookies(URL, httpCookies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -19,11 +20,7 @@ import (
|
|||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
|
||||||
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
|
||||||
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
|
||||||
contextutil "github.com/projectdiscovery/utils/context"
|
contextutil "github.com/projectdiscovery/utils/context"
|
||||||
"github.com/projectdiscovery/utils/errkit"
|
"github.com/projectdiscovery/utils/errkit"
|
||||||
errorutil "github.com/projectdiscovery/utils/errors"
|
errorutil "github.com/projectdiscovery/utils/errors"
|
||||||
@ -48,7 +45,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExecuteActions executes a list of actions on a page.
|
// ExecuteActions executes a list of actions on a page.
|
||||||
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (outData ActionData, err error) {
|
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (outData ActionData, err error) {
|
||||||
outData = make(ActionData)
|
outData = make(ActionData)
|
||||||
// waitFuncs are function that needs to be executed after navigation
|
// waitFuncs are function that needs to be executed after navigation
|
||||||
// typically used for waitEvent
|
// typically used for waitEvent
|
||||||
@ -69,7 +66,7 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, var
|
|||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
switch act.ActionType.ActionType {
|
switch act.ActionType.ActionType {
|
||||||
case ActionNavigate:
|
case ActionNavigate:
|
||||||
err = p.NavigateURL(act, outData, variables)
|
err = p.NavigateURL(act, outData)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// if navigation successful trigger all waitFuncs (if any)
|
// if navigation successful trigger all waitFuncs (if any)
|
||||||
for _, waitFunc := range waitFuncs {
|
for _, waitFunc := range waitFuncs {
|
||||||
@ -176,7 +173,7 @@ func (p *Page) WaitVisible(act *Action, out ActionData) error {
|
|||||||
return errors.Wrap(err, "Wrong timeout given")
|
return errors.Wrap(err, "Wrong timeout given")
|
||||||
}
|
}
|
||||||
|
|
||||||
pollTime, err := getPollTime(p, act)
|
pollTime, err := getTimeParameter(p, act, "pollTime", 100, time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Wrong polling time given")
|
return errors.Wrap(err, "Wrong polling time given")
|
||||||
}
|
}
|
||||||
@ -236,151 +233,220 @@ func getNavigationFunc(p *Page, act *Action, event proto.PageLifecycleEventName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTimeout(p *Page, act *Action) (time.Duration, error) {
|
func getTimeout(p *Page, act *Action) (time.Duration, error) {
|
||||||
return geTimeParameter(p, act, "timeout", 3, time.Second)
|
return getTimeParameter(p, act, "timeout", 5, time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPollTime(p *Page, act *Action) (time.Duration, error) {
|
// getTimeParameter returns a time parameter from an action. It first tries to
|
||||||
return geTimeParameter(p, act, "pollTime", 100, time.Millisecond)
|
// get the parameter as an integer, then as a time.Duration, and finally falls
|
||||||
}
|
// back to the default value (multiplied by the unit).
|
||||||
|
func getTimeParameter(p *Page, act *Action, argName string, defaultValue, unit time.Duration) (time.Duration, error) {
|
||||||
func geTimeParameter(p *Page, act *Action, parameterName string, defaultValue time.Duration, duration time.Duration) (time.Duration, error) {
|
argValue, err := p.getActionArg(act, argName)
|
||||||
pollTimeString := p.getActionArgWithDefaultValues(act, parameterName)
|
|
||||||
if pollTimeString == "" {
|
|
||||||
return defaultValue * duration, nil
|
|
||||||
}
|
|
||||||
timeout, err := strconv.Atoi(pollTimeString)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Duration(0), err
|
return time.Duration(0), err
|
||||||
}
|
}
|
||||||
return time.Duration(timeout) * duration, nil
|
|
||||||
|
convertedValue, err := strconv.Atoi(argValue)
|
||||||
|
if err == nil {
|
||||||
|
return time.Duration(convertedValue) * unit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to time.ParseDuration
|
||||||
|
parsedTimeValue, err := time.ParseDuration(argValue)
|
||||||
|
if err == nil {
|
||||||
|
return parsedTimeValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue * unit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionAddHeader executes a AddHeader action.
|
// ActionAddHeader executes a AddHeader action.
|
||||||
func (p *Page) ActionAddHeader(act *Action, out ActionData) error {
|
func (p *Page) ActionAddHeader(act *Action, out ActionData) error {
|
||||||
in := p.getActionArgWithDefaultValues(act, "part")
|
|
||||||
|
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
|
||||||
args["value"] = p.getActionArgWithDefaultValues(act, "value")
|
part, err := p.getActionArg(act, "part")
|
||||||
p.rules = append(p.rules, rule{Action: ActionAddHeader, Part: in, Args: args})
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["key"], err = p.getActionArg(act, "key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["value"], err = p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rules = append(p.rules, rule{
|
||||||
|
Action: ActionAddHeader,
|
||||||
|
Part: part,
|
||||||
|
Args: args,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionSetHeader executes a SetHeader action.
|
// ActionSetHeader executes a SetHeader action.
|
||||||
func (p *Page) ActionSetHeader(act *Action, out ActionData) error {
|
func (p *Page) ActionSetHeader(act *Action, out ActionData) error {
|
||||||
in := p.getActionArgWithDefaultValues(act, "part")
|
|
||||||
|
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
|
||||||
args["value"] = p.getActionArgWithDefaultValues(act, "value")
|
part, err := p.getActionArg(act, "part")
|
||||||
p.rules = append(p.rules, rule{Action: ActionSetHeader, Part: in, Args: args})
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["key"], err = p.getActionArg(act, "key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["value"], err = p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rules = append(p.rules, rule{
|
||||||
|
Action: ActionSetHeader,
|
||||||
|
Part: part,
|
||||||
|
Args: args,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionDeleteHeader executes a DeleteHeader action.
|
// ActionDeleteHeader executes a DeleteHeader action.
|
||||||
func (p *Page) ActionDeleteHeader(act *Action, out ActionData) error {
|
func (p *Page) ActionDeleteHeader(act *Action, out ActionData) error {
|
||||||
in := p.getActionArgWithDefaultValues(act, "part")
|
|
||||||
|
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
|
||||||
p.rules = append(p.rules, rule{Action: ActionDeleteHeader, Part: in, Args: args})
|
part, err := p.getActionArg(act, "part")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["key"], err = p.getActionArg(act, "key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rules = append(p.rules, rule{
|
||||||
|
Action: ActionDeleteHeader,
|
||||||
|
Part: part,
|
||||||
|
Args: args,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionSetBody executes a SetBody action.
|
// ActionSetBody executes a SetBody action.
|
||||||
func (p *Page) ActionSetBody(act *Action, out ActionData) error {
|
func (p *Page) ActionSetBody(act *Action, out ActionData) error {
|
||||||
in := p.getActionArgWithDefaultValues(act, "part")
|
|
||||||
|
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
args["body"] = p.getActionArgWithDefaultValues(act, "body")
|
|
||||||
p.rules = append(p.rules, rule{Action: ActionSetBody, Part: in, Args: args})
|
part, err := p.getActionArg(act, "part")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["body"], err = p.getActionArg(act, "body")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rules = append(p.rules, rule{
|
||||||
|
Action: ActionSetBody,
|
||||||
|
Part: part,
|
||||||
|
Args: args,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionSetMethod executes an SetMethod action.
|
// ActionSetMethod executes an SetMethod action.
|
||||||
func (p *Page) ActionSetMethod(act *Action, out ActionData) error {
|
func (p *Page) ActionSetMethod(act *Action, out ActionData) error {
|
||||||
in := p.getActionArgWithDefaultValues(act, "part")
|
|
||||||
|
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
args["method"] = p.getActionArgWithDefaultValues(act, "method")
|
|
||||||
p.rules = append(p.rules, rule{Action: ActionSetMethod, Part: in, Args: args, Once: &sync.Once{}})
|
part, err := p.getActionArg(act, "part")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["method"], err = p.getActionArg(act, "method")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rules = append(p.rules, rule{
|
||||||
|
Action: ActionSetMethod,
|
||||||
|
Part: part,
|
||||||
|
Args: args,
|
||||||
|
Once: &sync.Once{},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NavigateURL executes an ActionLoadURL actions loading a URL for the page.
|
// NavigateURL executes an ActionLoadURL actions loading a URL for the page.
|
||||||
func (p *Page) NavigateURL(action *Action, out ActionData, allvars map[string]interface{}) error {
|
func (p *Page) NavigateURL(action *Action, out ActionData) error {
|
||||||
// input <- is input url from cli
|
url, err := p.getActionArg(action, "url")
|
||||||
// target <- is the url from template (ex: {{BaseURL}}/test)
|
|
||||||
input, err := urlutil.Parse(p.input.MetaInput.Input)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorutil.NewWithErr(err).Msgf("could not parse url %s", p.input.MetaInput.Input)
|
return err
|
||||||
}
|
}
|
||||||
target := p.getActionArgWithDefaultValues(action, "url")
|
|
||||||
if target == "" {
|
if url == "" {
|
||||||
return errinvalidArguments
|
return errinvalidArguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// if target contains port ex: {{BaseURL}}:8080 use port specified in input
|
parsedURL, err := urlutil.ParseURL(url, true)
|
||||||
input, target = httputil.UpdateURLPortFromPayload(input, target)
|
|
||||||
hasTrailingSlash := httputil.HasTrailingSlash(target)
|
|
||||||
|
|
||||||
// create vars from input url
|
|
||||||
defaultReqVars := protocolutils.GenerateVariables(input, hasTrailingSlash, contextargs.GenerateVariables(p.input))
|
|
||||||
// merge all variables
|
|
||||||
// Note: ideally we should evaluate all available variables with reqvars
|
|
||||||
// but due to cyclic dependency between packages `engine` and `protocols`
|
|
||||||
// allvars are evaluated,merged and passed from headless package itself
|
|
||||||
// TODO: remove cyclic dependency between packages `engine` and `protocols`
|
|
||||||
allvars = generators.MergeMaps(allvars, defaultReqVars)
|
|
||||||
|
|
||||||
if vardump.EnableVarDump {
|
|
||||||
gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(allvars))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the target url with all variables
|
|
||||||
target, err = expressions.Evaluate(target, allvars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorutil.NewWithErr(err).Msgf("could not evaluate url %s", target)
|
return errorutil.NewWithTag("headless", "failed to parse url %v while creating http request", url)
|
||||||
}
|
|
||||||
|
|
||||||
reqURL, err := urlutil.ParseURL(target, true)
|
|
||||||
if err != nil {
|
|
||||||
return errorutil.NewWithTag("http", "failed to parse url %v while creating http request", target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== parameter automerge =====
|
// ===== parameter automerge =====
|
||||||
// while merging parameters first preference is given to target params
|
// while merging parameters first preference is given to target params
|
||||||
finalparams := input.Params.Clone()
|
finalparams := parsedURL.Params.Clone()
|
||||||
finalparams.Merge(reqURL.Params.Encode())
|
finalparams.Merge(p.inputURL.Params.Encode())
|
||||||
reqURL.Params = finalparams
|
parsedURL.Params = finalparams
|
||||||
|
|
||||||
// log all navigated requests
|
// log all navigated requests
|
||||||
p.instance.requestLog[action.GetArg("url")] = reqURL.String()
|
p.instance.requestLog[action.GetArg("url")] = parsedURL.String()
|
||||||
|
|
||||||
if err := p.page.Navigate(reqURL.String()); err != nil {
|
if err := p.page.Navigate(parsedURL.String()); err != nil {
|
||||||
return errorutil.NewWithErr(err).Msgf("could not navigate to url %s", reqURL.String())
|
return errorutil.NewWithErr(err).Msgf("could not navigate to url %s", parsedURL.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunScript runs a script on the loaded page
|
// RunScript runs a script on the loaded page
|
||||||
func (p *Page) RunScript(action *Action, out ActionData) error {
|
func (p *Page) RunScript(act *Action, out ActionData) error {
|
||||||
code := p.getActionArgWithDefaultValues(action, "code")
|
code, err := p.getActionArg(act, "code")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if code == "" {
|
if code == "" {
|
||||||
return errinvalidArguments
|
return errinvalidArguments
|
||||||
}
|
}
|
||||||
if p.getActionArgWithDefaultValues(action, "hook") == "true" {
|
|
||||||
|
hook, err := p.getActionArg(act, "hook")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hook == "true" {
|
||||||
if _, err := p.page.EvalOnNewDocument(code); err != nil {
|
if _, err := p.page.EvalOnNewDocument(code); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := p.page.Eval(code)
|
data, err := p.page.Eval(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if data != nil && action.Name != "" {
|
|
||||||
out[action.Name] = data.Value.String()
|
if data != nil && act.Name != "" {
|
||||||
|
out[act.Name] = data.Value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +467,12 @@ func (p *Page) ClickElement(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// KeyboardAction executes a keyboard action on the page.
|
// KeyboardAction executes a keyboard action on the page.
|
||||||
func (p *Page) KeyboardAction(act *Action, out ActionData) error {
|
func (p *Page) KeyboardAction(act *Action, out ActionData) error {
|
||||||
return p.page.Keyboard.Type([]input.Key(p.getActionArgWithDefaultValues(act, "keys"))...)
|
keys, err := p.getActionArg(act, "keys")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.page.Keyboard.Type([]input.Key(keys)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RightClickElement executes right click actions for an element.
|
// RightClickElement executes right click actions for an element.
|
||||||
@ -421,16 +492,26 @@ func (p *Page) RightClickElement(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// Screenshot executes screenshot action on a page
|
// Screenshot executes screenshot action on a page
|
||||||
func (p *Page) Screenshot(act *Action, out ActionData) error {
|
func (p *Page) Screenshot(act *Action, out ActionData) error {
|
||||||
to := p.getActionArgWithDefaultValues(act, "to")
|
to, err := p.getActionArg(act, "to")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if to == "" {
|
if to == "" {
|
||||||
to = ksuid.New().String()
|
to = ksuid.New().String()
|
||||||
if act.Name != "" {
|
if act.Name != "" {
|
||||||
out[act.Name] = to
|
out[act.Name] = to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var err error
|
|
||||||
if p.getActionArgWithDefaultValues(act, "fullpage") == "true" {
|
fullpage, err := p.getActionArg(act, "fullpage")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullpage == "true" {
|
||||||
data, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})
|
data, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})
|
||||||
} else {
|
} else {
|
||||||
data, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})
|
data, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})
|
||||||
@ -438,25 +519,32 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not take screenshot")
|
return errors.Wrap(err, "could not take screenshot")
|
||||||
}
|
}
|
||||||
targetPath := p.getActionArgWithDefaultValues(act, "to")
|
|
||||||
targetPath, err = fileutil.CleanPath(targetPath)
|
to, err = fileutil.CleanPath(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorutil.New("could not clean output screenshot path %s", targetPath)
|
return errorutil.New("could not clean output screenshot path %s", to)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow if targetPath is child of current working directory
|
// allow if targetPath is child of current working directory
|
||||||
if !protocolstate.IsLFAAllowed() {
|
if !protocolstate.IsLFAAllowed() {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorutil.NewWithErr(err).Msgf("could not get current working directory")
|
return errorutil.NewWithErr(err).Msgf("could not get current working directory")
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(targetPath, cwd) {
|
|
||||||
|
if !strings.HasPrefix(to, cwd) {
|
||||||
// writing outside of cwd requires -lfa flag
|
// writing outside of cwd requires -lfa flag
|
||||||
return ErrLFAccessDenied
|
return ErrLFAccessDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mkdir, err := p.getActionArg(act, "mkdir")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// edgecase create directory if mkdir=true and path contains directory
|
// edgecase create directory if mkdir=true and path contains directory
|
||||||
if p.getActionArgWithDefaultValues(act, "mkdir") == "true" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {
|
if mkdir == "true" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {
|
||||||
// creates new directory if needed based on path `to`
|
// creates new directory if needed based on path `to`
|
||||||
// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
|
// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
|
||||||
if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
|
if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
|
||||||
@ -465,7 +553,7 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// actual file path to write
|
// actual file path to write
|
||||||
filePath := targetPath
|
filePath := to
|
||||||
if !strings.HasSuffix(filePath, ".png") {
|
if !strings.HasSuffix(filePath, ".png") {
|
||||||
filePath += ".png"
|
filePath += ".png"
|
||||||
}
|
}
|
||||||
@ -484,7 +572,10 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// InputElement executes input element actions for an element.
|
// InputElement executes input element actions for an element.
|
||||||
func (p *Page) InputElement(act *Action, out ActionData) error {
|
func (p *Page) InputElement(act *Action, out ActionData) error {
|
||||||
value := p.getActionArgWithDefaultValues(act, "value")
|
value, err := p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return errinvalidArguments
|
return errinvalidArguments
|
||||||
}
|
}
|
||||||
@ -503,7 +594,10 @@ func (p *Page) InputElement(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// TimeInputElement executes time input on an element
|
// TimeInputElement executes time input on an element
|
||||||
func (p *Page) TimeInputElement(act *Action, out ActionData) error {
|
func (p *Page) TimeInputElement(act *Action, out ActionData) error {
|
||||||
value := p.getActionArgWithDefaultValues(act, "value")
|
value, err := p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return errinvalidArguments
|
return errinvalidArguments
|
||||||
}
|
}
|
||||||
@ -526,7 +620,10 @@ func (p *Page) TimeInputElement(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// SelectInputElement executes select input statement action on a element
|
// SelectInputElement executes select input statement action on a element
|
||||||
func (p *Page) SelectInputElement(act *Action, out ActionData) error {
|
func (p *Page) SelectInputElement(act *Action, out ActionData) error {
|
||||||
value := p.getActionArgWithDefaultValues(act, "value")
|
value, err := p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return errinvalidArguments
|
return errinvalidArguments
|
||||||
}
|
}
|
||||||
@ -538,14 +635,26 @@ func (p *Page) SelectInputElement(act *Action, out ActionData) error {
|
|||||||
return errors.Wrap(err, errCouldNotScroll)
|
return errors.Wrap(err, errCouldNotScroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedBool := false
|
var selectedBool bool
|
||||||
if p.getActionArgWithDefaultValues(act, "selected") == "true" {
|
|
||||||
|
selected, err := p.getActionArg(act, "selected")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected == "true" {
|
||||||
selectedBool = true
|
selectedBool = true
|
||||||
}
|
}
|
||||||
by := p.getActionArgWithDefaultValues(act, "selector")
|
|
||||||
if err := element.Select([]string{value}, selectedBool, selectorBy(by)); err != nil {
|
selector, err := p.getActionArg(act, "selector")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := element.Select([]string{value}, selectedBool, selectorBy(selector)); err != nil {
|
||||||
return errors.Wrap(err, "could not select input")
|
return errors.Wrap(err, "could not select input")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,14 +712,21 @@ func (p *Page) FilesInput(act *Action, out ActionData) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, errCouldNotGetElement)
|
return errors.Wrap(err, errCouldNotGetElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = element.ScrollIntoView(); err != nil {
|
if err = element.ScrollIntoView(); err != nil {
|
||||||
return errors.Wrap(err, errCouldNotScroll)
|
return errors.Wrap(err, errCouldNotScroll)
|
||||||
}
|
}
|
||||||
value := p.getActionArgWithDefaultValues(act, "value")
|
|
||||||
|
value, err := p.getActionArg(act, "value")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
filesPaths := strings.Split(value, ",")
|
filesPaths := strings.Split(value, ",")
|
||||||
|
|
||||||
if err := element.SetFiles(filesPaths); err != nil {
|
if err := element.SetFiles(filesPaths); err != nil {
|
||||||
return errors.Wrap(err, "could not set files")
|
return errors.Wrap(err, "could not set files")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,19 +736,32 @@ func (p *Page) ExtractElement(act *Action, out ActionData) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, errCouldNotGetElement)
|
return errors.Wrap(err, errCouldNotGetElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = element.ScrollIntoView(); err != nil {
|
if err = element.ScrollIntoView(); err != nil {
|
||||||
return errors.Wrap(err, errCouldNotScroll)
|
return errors.Wrap(err, errCouldNotScroll)
|
||||||
}
|
}
|
||||||
switch p.getActionArgWithDefaultValues(act, "target") {
|
|
||||||
|
target, err := p.getActionArg(act, "target")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch target {
|
||||||
case "attribute":
|
case "attribute":
|
||||||
attrName := p.getActionArgWithDefaultValues(act, "attribute")
|
attribute, err := p.getActionArg(act, "attribute")
|
||||||
if attrName == "" {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if attribute == "" {
|
||||||
return errors.New("attribute can't be empty")
|
return errors.New("attribute can't be empty")
|
||||||
}
|
}
|
||||||
attrValue, err := element.Attribute(attrName)
|
|
||||||
|
attrValue, err := element.Attribute(attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not get attribute")
|
return errors.Wrap(err, "could not get attribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
if act.Name != "" {
|
if act.Name != "" {
|
||||||
out[act.Name] = *attrValue
|
out[act.Name] = *attrValue
|
||||||
}
|
}
|
||||||
@ -641,6 +770,7 @@ func (p *Page) ExtractElement(act *Action, out ActionData) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not get element text node")
|
return errors.Wrap(err, "could not get element text node")
|
||||||
}
|
}
|
||||||
|
|
||||||
if act.Name != "" {
|
if act.Name != "" {
|
||||||
out[act.Name] = text
|
out[act.Name] = text
|
||||||
}
|
}
|
||||||
@ -650,30 +780,33 @@ func (p *Page) ExtractElement(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// WaitEvent waits for an event to happen on the page.
|
// WaitEvent waits for an event to happen on the page.
|
||||||
func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
|
func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
|
||||||
event := p.getActionArgWithDefaultValues(act, "event")
|
event, err := p.getActionArg(act, "event")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if event == "" {
|
if event == "" {
|
||||||
return nil, errors.New("event not recognized")
|
return nil, errors.New("event not recognized")
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitEvent proto.Event
|
var waitEvent proto.Event
|
||||||
|
|
||||||
gotType := proto.GetType(event)
|
gotType := proto.GetType(event)
|
||||||
if gotType == nil {
|
if gotType == nil {
|
||||||
return nil, errorutil.New("event %v does not exist", event)
|
return nil, errorutil.New("event %q does not exist", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
|
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errorutil.New("event %v is not a page event", event)
|
return nil, errorutil.New("event %q is not a page event", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitEvent = tmp
|
waitEvent = tmp
|
||||||
maxDuration := 10 * time.Second // 10 sec is max wait duration for any event
|
|
||||||
|
|
||||||
// allow user to specify max-duration for wait-event
|
// allow user to specify max-duration for wait-event
|
||||||
if value := p.getActionArgWithDefaultValues(act, "max-duration"); value != "" {
|
maxDuration, err := getTimeParameter(p, act, "max-duration", 5, time.Second)
|
||||||
var err error
|
if err != nil {
|
||||||
maxDuration, err = time.ParseDuration(value)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, errorutil.NewWithErr(err).Msgf("could not parse max-duration")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just wait the event to happen
|
// Just wait the event to happen
|
||||||
@ -681,23 +814,20 @@ func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
|
|||||||
// execute actual wait event
|
// execute actual wait event
|
||||||
ctx, cancel := context.WithTimeoutCause(context.Background(), maxDuration, ErrActionExecDealine)
|
ctx, cancel := context.WithTimeoutCause(context.Background(), maxDuration, ErrActionExecDealine)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = contextutil.ExecFunc(ctx, p.page.WaitEvent(waitEvent))
|
err = contextutil.ExecFunc(ctx, p.page.WaitEvent(waitEvent))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitFunc, nil
|
return waitFunc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDialog handles JavaScript dialog (alert, confirm, prompt, or onbeforeunload).
|
// HandleDialog handles JavaScript dialog (alert, confirm, prompt, or onbeforeunload).
|
||||||
func (p *Page) HandleDialog(act *Action, out ActionData) error {
|
func (p *Page) HandleDialog(act *Action, out ActionData) error {
|
||||||
maxDuration := 10 * time.Second
|
maxDuration, err := getTimeParameter(p, act, "max-duration", 10, time.Second)
|
||||||
|
if err != nil {
|
||||||
if dur := p.getActionArgWithDefaultValues(act, "max-duration"); dur != "" {
|
return err
|
||||||
var err error
|
|
||||||
|
|
||||||
maxDuration, err = time.ParseDuration(dur)
|
|
||||||
if err != nil {
|
|
||||||
return errorutil.NewWithErr(err).Msgf("could not parse max-duration")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), maxDuration)
|
ctx, cancel := context.WithTimeout(context.Background(), maxDuration)
|
||||||
@ -766,15 +896,13 @@ func (p *Page) DebugAction(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
// SleepAction sleeps on the page for a specified duration
|
// SleepAction sleeps on the page for a specified duration
|
||||||
func (p *Page) SleepAction(act *Action, out ActionData) error {
|
func (p *Page) SleepAction(act *Action, out ActionData) error {
|
||||||
seconds := act.Data["duration"]
|
duration, err := getTimeParameter(p, act, "duration", 5, time.Second)
|
||||||
if seconds == "" {
|
|
||||||
seconds = "5"
|
|
||||||
}
|
|
||||||
parsed, err := strconv.Atoi(seconds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(parsed) * time.Second)
|
|
||||||
|
time.Sleep(duration)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -792,20 +920,28 @@ func selectorBy(selector string) rod.SelectorType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) getActionArgWithDefaultValues(action *Action, arg string) string {
|
func (p *Page) getActionArg(action *Action, arg string) (string, error) {
|
||||||
return p.getActionArgWithValues(action, arg, generators.MergeMaps(
|
var err error
|
||||||
generators.BuildPayloadFromOptions(p.instance.browser.options),
|
|
||||||
p.payloads,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Page) getActionArgWithValues(action *Action, arg string, values map[string]interface{}) string {
|
|
||||||
argValue := action.GetArg(arg)
|
argValue := action.GetArg(arg)
|
||||||
argValue = replaceWithValues(argValue, values)
|
|
||||||
if p.instance.interactsh != nil {
|
if p.instance.interactsh != nil {
|
||||||
var interactshURLs []string
|
var interactshURLs []string
|
||||||
argValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)
|
argValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)
|
||||||
p.addInteractshURL(interactshURLs...)
|
p.addInteractshURL(interactshURLs...)
|
||||||
}
|
}
|
||||||
return argValue
|
|
||||||
|
exprs := getExpressions(argValue, p.variables)
|
||||||
|
|
||||||
|
err = expressions.ContainsUnresolvedVariables(exprs...)
|
||||||
|
if err != nil {
|
||||||
|
return "", errorutil.NewWithErr(err).Msgf("argument %q, value: %q", arg, argValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
argValue, err = expressions.Evaluate(argValue, p.variables)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get value for argument %q: %s", arg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return argValue, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ func (p *Page) routingRuleHandler(httpClient *http.Client) func(ctx *rod.Hijack)
|
|||||||
// 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 !p.options.DisableCookie {
|
if !p.options.DisableCookie {
|
||||||
if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
if cookies := p.ctx.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
||||||
httpClient.Jar.SetCookies(ctx.Request.URL(), cookies)
|
httpClient.Jar.SetCookies(ctx.Request.URL(), cookies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ func (p *Page) routingRuleHandler(httpClient *http.Client) func(ctx *rod.Hijack)
|
|||||||
// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
|
// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
|
||||||
// keeps existing one if not present
|
// keeps existing one if not present
|
||||||
if cookies := httpClient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
if cookies := httpClient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
|
||||||
p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies)
|
p.ctx.CookieJar.SetCookies(ctx.Request.URL(), cookies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker"
|
||||||
"github.com/valyala/fasttemplate"
|
"github.com/valyala/fasttemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// replaceWithValues replaces the template markers with the values
|
||||||
|
//
|
||||||
|
// Deprecated: Not used anymore.
|
||||||
|
// nolint: unused
|
||||||
func replaceWithValues(data string, values map[string]interface{}) string {
|
func replaceWithValues(data string, values map[string]interface{}) string {
|
||||||
return fasttemplate.ExecuteStringStd(data, marker.ParenthesisOpen, marker.ParenthesisClose, values)
|
return fasttemplate.ExecuteStringStd(data, marker.ParenthesisOpen, marker.ParenthesisClose, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExpressions(data string, values map[string]interface{}) []string {
|
||||||
|
return expressions.FindExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, values)
|
||||||
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
|
||||||
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
||||||
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||||
@ -120,10 +119,6 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p
|
|||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
|
|
||||||
if vardump.EnableVarDump {
|
|
||||||
gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(payloads))
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.SetInteractsh(request.options.Interactsh)
|
instance.SetInteractsh(request.options.Interactsh)
|
||||||
|
|
||||||
if _, err := url.Parse(input.MetaInput.Input); err != nil {
|
if _, err := url.Parse(input.MetaInput.Input); err != nil {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user