2021-02-21 16:31:34 +05:30
|
|
|
package engine
|
|
|
|
|
|
|
|
|
|
import (
|
2021-10-13 20:08:10 +03:00
|
|
|
"context"
|
2022-08-25 13:22:08 +02:00
|
|
|
"os"
|
2023-03-24 00:44:32 +05:30
|
|
|
"path/filepath"
|
2023-12-06 19:08:26 +05:30
|
|
|
"reflect"
|
2021-02-21 16:31:34 +05:30
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2023-03-04 07:57:26 +01:00
|
|
|
"sync"
|
2021-02-21 16:31:34 +05:30
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/go-rod/rod"
|
2022-06-22 00:31:08 +05:30
|
|
|
"github.com/go-rod/rod/lib/input"
|
2021-02-21 16:31:34 +05:30
|
|
|
"github.com/go-rod/rod/lib/proto"
|
2021-10-13 20:08:10 +03:00
|
|
|
"github.com/go-rod/rod/lib/utils"
|
2021-02-21 16:31:34 +05:30
|
|
|
"github.com/pkg/errors"
|
2023-03-24 00:44:32 +05:30
|
|
|
"github.com/projectdiscovery/gologger"
|
2023-10-17 17:44:13 +05:30
|
|
|
"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/generators"
|
2024-01-05 03:23:08 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
2023-10-17 17:44:13 +05:30
|
|
|
"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"
|
2023-12-06 19:08:26 +05:30
|
|
|
contextutil "github.com/projectdiscovery/utils/context"
|
2023-03-24 00:44:32 +05:30
|
|
|
errorutil "github.com/projectdiscovery/utils/errors"
|
|
|
|
|
fileutil "github.com/projectdiscovery/utils/file"
|
|
|
|
|
folderutil "github.com/projectdiscovery/utils/folder"
|
|
|
|
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
2023-07-28 19:28:20 +05:30
|
|
|
urlutil "github.com/projectdiscovery/utils/url"
|
2021-02-21 16:31:34 +05:30
|
|
|
"github.com/segmentio/ksuid"
|
|
|
|
|
)
|
|
|
|
|
|
2022-05-12 05:10:14 -05:00
|
|
|
var (
|
2023-08-25 18:30:46 +05:30
|
|
|
errinvalidArguments = errorutil.New("invalid arguments provided")
|
|
|
|
|
ErrLFAccessDenied = errorutil.New("Use -allow-local-file-access flag to enable local file access")
|
2022-05-12 05:10:14 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2023-01-05 12:56:18 +01:00
|
|
|
errCouldNotGetElement = "could not get element"
|
|
|
|
|
errCouldNotScroll = "could not scroll into view"
|
|
|
|
|
errElementDidNotAppear = "Element did not appear in the given amount of time"
|
2022-05-12 05:10:14 -05:00
|
|
|
)
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
// ExecuteActions executes a list of actions on a page.
|
2023-12-06 19:08:26 +05:30
|
|
|
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (outData map[string]string, err error) {
|
|
|
|
|
outData = make(map[string]string)
|
|
|
|
|
// waitFuncs are function that needs to be executed after navigation
|
|
|
|
|
// typically used for waitEvent
|
|
|
|
|
waitFuncs := make([]func() error, 0)
|
|
|
|
|
|
|
|
|
|
// avoid any future panics caused due to go-rod library
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
err = errorutil.New("panic on headless action: %v", r)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
for _, act := range actions {
|
2021-11-18 14:11:10 -06:00
|
|
|
switch act.ActionType.ActionType {
|
2021-02-21 16:31:34 +05:30
|
|
|
case ActionNavigate:
|
2023-07-28 19:28:20 +05:30
|
|
|
err = p.NavigateURL(act, outData, variables)
|
2023-12-06 19:08:26 +05:30
|
|
|
if err == nil {
|
|
|
|
|
// if navigation successful trigger all waitFuncs (if any)
|
|
|
|
|
for _, waitFunc := range waitFuncs {
|
|
|
|
|
if waitFunc != nil {
|
|
|
|
|
if err := waitFunc(); err != nil {
|
|
|
|
|
return nil, errorutil.NewWithErr(err).Msgf("error occurred while executing waitFunc")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
case ActionScript:
|
|
|
|
|
err = p.RunScript(act, outData)
|
|
|
|
|
case ActionClick:
|
|
|
|
|
err = p.ClickElement(act, outData)
|
|
|
|
|
case ActionRightClick:
|
|
|
|
|
err = p.RightClickElement(act, outData)
|
|
|
|
|
case ActionTextInput:
|
|
|
|
|
err = p.InputElement(act, outData)
|
|
|
|
|
case ActionScreenshot:
|
|
|
|
|
err = p.Screenshot(act, outData)
|
|
|
|
|
case ActionTimeInput:
|
|
|
|
|
err = p.TimeInputElement(act, outData)
|
|
|
|
|
case ActionSelectInput:
|
|
|
|
|
err = p.SelectInputElement(act, outData)
|
|
|
|
|
case ActionWaitLoad:
|
|
|
|
|
err = p.WaitLoad(act, outData)
|
|
|
|
|
case ActionGetResource:
|
|
|
|
|
err = p.GetResource(act, outData)
|
|
|
|
|
case ActionExtract:
|
2021-10-13 20:08:10 +03:00
|
|
|
err = p.ExtractElement(act, outData)
|
2021-02-21 16:31:34 +05:30
|
|
|
case ActionWaitEvent:
|
2023-12-06 19:08:26 +05:30
|
|
|
var waitFunc func() error
|
|
|
|
|
waitFunc, err = p.WaitEvent(act, outData)
|
|
|
|
|
if waitFunc != nil {
|
|
|
|
|
waitFuncs = append(waitFuncs, waitFunc)
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
case ActionFilesInput:
|
2023-08-25 18:30:46 +05:30
|
|
|
if p.options.Options.AllowLocalFileAccess {
|
|
|
|
|
err = p.FilesInput(act, outData)
|
|
|
|
|
} else {
|
|
|
|
|
err = ErrLFAccessDenied
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
case ActionAddHeader:
|
|
|
|
|
err = p.ActionAddHeader(act, outData)
|
|
|
|
|
case ActionSetHeader:
|
|
|
|
|
err = p.ActionSetHeader(act, outData)
|
|
|
|
|
case ActionDeleteHeader:
|
|
|
|
|
err = p.ActionDeleteHeader(act, outData)
|
|
|
|
|
case ActionSetBody:
|
|
|
|
|
err = p.ActionSetBody(act, outData)
|
|
|
|
|
case ActionSetMethod:
|
|
|
|
|
err = p.ActionSetMethod(act, outData)
|
|
|
|
|
case ActionKeyboard:
|
|
|
|
|
err = p.KeyboardAction(act, outData)
|
2021-03-01 14:20:56 +05:30
|
|
|
case ActionDebug:
|
|
|
|
|
err = p.DebugAction(act, outData)
|
|
|
|
|
case ActionSleep:
|
|
|
|
|
err = p.SleepAction(act, outData)
|
2021-10-09 08:38:42 +02:00
|
|
|
case ActionWaitVisible:
|
|
|
|
|
err = p.WaitVisible(act, outData)
|
2021-02-21 16:31:34 +05:30
|
|
|
default:
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
2021-02-26 13:13:11 +05:30
|
|
|
return nil, errors.Wrap(err, "error occurred executing action")
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return outData, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 12:56:18 +01:00
|
|
|
type rule struct {
|
2023-03-04 07:57:26 +01:00
|
|
|
*sync.Once
|
2021-02-21 16:31:34 +05:30
|
|
|
Action ActionType
|
|
|
|
|
Part string
|
|
|
|
|
Args map[string]string
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 08:38:42 +02:00
|
|
|
// WaitVisible waits until an element appears.
|
|
|
|
|
func (p *Page) WaitVisible(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
timeout, err := getTimeout(p, act)
|
2021-10-09 08:38:42 +02:00
|
|
|
if err != nil {
|
2021-10-13 20:08:10 +03:00
|
|
|
return errors.Wrap(err, "Wrong timeout given")
|
2021-10-09 08:38:42 +02:00
|
|
|
}
|
2021-10-13 20:08:10 +03:00
|
|
|
|
2022-01-31 02:25:14 +01:00
|
|
|
pollTime, err := getPollTime(p, act)
|
2021-10-13 20:08:10 +03:00
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "Wrong polling time given")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element, _ := p.Sleeper(pollTime, timeout).
|
|
|
|
|
Timeout(timeout).
|
|
|
|
|
pageElementBy(act.Data)
|
|
|
|
|
|
|
|
|
|
if element != nil {
|
|
|
|
|
if err := element.WaitVisible(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errElementDidNotAppear)
|
2021-10-13 20:08:10 +03:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.New(errElementDidNotAppear)
|
2021-10-09 12:26:18 +02:00
|
|
|
}
|
2021-10-13 20:08:10 +03:00
|
|
|
|
2021-10-09 12:26:18 +02:00
|
|
|
return nil
|
2021-10-09 08:38:42 +02:00
|
|
|
}
|
|
|
|
|
|
2021-10-13 20:08:10 +03:00
|
|
|
func (p *Page) Sleeper(pollTimeout, timeout time.Duration) *Page {
|
|
|
|
|
page := *p
|
|
|
|
|
page.page = page.Page().Sleeper(func() utils.Sleeper {
|
|
|
|
|
return createBackOffSleeper(pollTimeout, timeout)
|
|
|
|
|
})
|
|
|
|
|
return &page
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Page) Timeout(timeout time.Duration) *Page {
|
|
|
|
|
page := *p
|
|
|
|
|
page.page = page.Page().Timeout(timeout)
|
|
|
|
|
return &page
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {
|
|
|
|
|
backoffSleeper := utils.BackoffSleeper(pollTimeout, timeout, func(duration time.Duration) time.Duration {
|
|
|
|
|
return duration
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return func(ctx context.Context) error {
|
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return backoffSleeper(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 02:25:14 +01:00
|
|
|
func getTimeout(p *Page, act *Action) (time.Duration, error) {
|
|
|
|
|
return geTimeParameter(p, act, "timeout", 3, time.Second)
|
2021-10-13 20:08:10 +03:00
|
|
|
}
|
|
|
|
|
|
2022-01-31 02:25:14 +01:00
|
|
|
func getPollTime(p *Page, act *Action) (time.Duration, error) {
|
|
|
|
|
return geTimeParameter(p, act, "pollTime", 100, time.Millisecond)
|
2021-10-13 20:08:10 +03:00
|
|
|
}
|
|
|
|
|
|
2022-01-31 02:25:14 +01:00
|
|
|
func geTimeParameter(p *Page, act *Action, parameterName string, defaultValue time.Duration, duration time.Duration) (time.Duration, error) {
|
|
|
|
|
pollTimeString := p.getActionArgWithDefaultValues(act, parameterName)
|
2021-10-13 20:08:10 +03:00
|
|
|
if pollTimeString == "" {
|
|
|
|
|
return defaultValue * duration, nil
|
|
|
|
|
}
|
|
|
|
|
timeout, err := strconv.Atoi(pollTimeString)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return time.Duration(0), err
|
|
|
|
|
}
|
|
|
|
|
return time.Duration(timeout) * duration, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
// ActionAddHeader executes a AddHeader action.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) ActionAddHeader(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
in := p.getActionArgWithDefaultValues(act, "part")
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
args := make(map[string]string)
|
2022-01-31 02:25:14 +01:00
|
|
|
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
|
|
|
|
args["value"] = p.getActionArgWithDefaultValues(act, "value")
|
2023-01-05 12:56:18 +01:00
|
|
|
p.rules = append(p.rules, rule{Action: ActionAddHeader, Part: in, Args: args})
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionSetHeader executes a SetHeader action.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) ActionSetHeader(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
in := p.getActionArgWithDefaultValues(act, "part")
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
args := make(map[string]string)
|
2022-01-31 02:25:14 +01:00
|
|
|
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
|
|
|
|
args["value"] = p.getActionArgWithDefaultValues(act, "value")
|
2023-01-05 12:56:18 +01:00
|
|
|
p.rules = append(p.rules, rule{Action: ActionSetHeader, Part: in, Args: args})
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionDeleteHeader executes a DeleteHeader action.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) ActionDeleteHeader(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
in := p.getActionArgWithDefaultValues(act, "part")
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
args := make(map[string]string)
|
2022-01-31 02:25:14 +01:00
|
|
|
args["key"] = p.getActionArgWithDefaultValues(act, "key")
|
2023-01-05 12:56:18 +01:00
|
|
|
p.rules = append(p.rules, rule{Action: ActionDeleteHeader, Part: in, Args: args})
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionSetBody executes a SetBody action.
|
2023-06-26 19:25:51 +02:00
|
|
|
func (p *Page) ActionSetBody(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
in := p.getActionArgWithDefaultValues(act, "part")
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
args := make(map[string]string)
|
2022-01-31 02:25:14 +01:00
|
|
|
args["body"] = p.getActionArgWithDefaultValues(act, "body")
|
2023-01-05 12:56:18 +01:00
|
|
|
p.rules = append(p.rules, rule{Action: ActionSetBody, Part: in, Args: args})
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionSetMethod executes an SetMethod action.
|
2023-03-04 07:57:26 +01:00
|
|
|
func (p *Page) ActionSetMethod(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
in := p.getActionArgWithDefaultValues(act, "part")
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
args := make(map[string]string)
|
2022-01-31 02:25:14 +01:00
|
|
|
args["method"] = p.getActionArgWithDefaultValues(act, "method")
|
2023-03-04 07:57:26 +01:00
|
|
|
p.rules = append(p.rules, rule{Action: ActionSetMethod, Part: in, Args: args, Once: &sync.Once{}})
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NavigateURL executes an ActionLoadURL actions loading a URL for the page.
|
2023-07-28 19:28:20 +05:30
|
|
|
func (p *Page) NavigateURL(action *Action, out map[string]string, allvars map[string]interface{}) error {
|
|
|
|
|
// input <- is input url from cli
|
|
|
|
|
// target <- is the url from template (ex: {{BaseURL}}/test)
|
|
|
|
|
input, err := urlutil.Parse(p.input.MetaInput.Input)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errorutil.NewWithErr(err).Msgf("could not parse url %s", p.input.MetaInput.Input)
|
|
|
|
|
}
|
|
|
|
|
target := p.getActionArgWithDefaultValues(action, "url")
|
|
|
|
|
if target == "" {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errinvalidArguments
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-01-31 02:25:14 +01:00
|
|
|
|
2023-07-28 19:28:20 +05:30
|
|
|
// if target contains port ex: {{BaseURL}}:8080 use port specified in input
|
|
|
|
|
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 {
|
2023-10-13 13:17:27 +05:30
|
|
|
gologger.Debug().Msgf("Headless Protocol request variables: \n%s\n", vardump.DumpVariables(allvars))
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
|
2023-07-28 19:28:20 +05:30
|
|
|
// Evaluate the target url with all variables
|
|
|
|
|
target, err = expressions.Evaluate(target, allvars)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errorutil.NewWithErr(err).Msgf("could not evaluate url %s", target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 =====
|
|
|
|
|
// while merging parameters first preference is given to target params
|
|
|
|
|
finalparams := input.Params.Clone()
|
|
|
|
|
finalparams.Merge(reqURL.Params.Encode())
|
|
|
|
|
reqURL.Params = finalparams
|
|
|
|
|
|
|
|
|
|
// log all navigated requests
|
|
|
|
|
p.instance.requestLog[action.GetArg("url")] = reqURL.String()
|
|
|
|
|
|
|
|
|
|
if err := p.page.Navigate(reqURL.String()); err != nil {
|
|
|
|
|
return errorutil.NewWithErr(err).Msgf("could not navigate to url %s", reqURL.String())
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RunScript runs a script on the loaded page
|
|
|
|
|
func (p *Page) RunScript(action *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
code := p.getActionArgWithDefaultValues(action, "code")
|
2021-02-21 16:31:34 +05:30
|
|
|
if code == "" {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errinvalidArguments
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-01-31 02:25:14 +01:00
|
|
|
if p.getActionArgWithDefaultValues(action, "hook") == "true" {
|
2021-02-21 16:31:34 +05:30
|
|
|
if _, err := p.page.EvalOnNewDocument(code); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
data, err := p.page.Eval(code)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-02-25 12:37:47 +05:30
|
|
|
if data != nil && action.Name != "" {
|
2021-02-21 16:31:34 +05:30
|
|
|
out[action.Name] = data.Value.String()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ClickElement executes click actions for an element.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) ClickElement(act *Action, out map[string]string) error {
|
2021-02-21 16:31:34 +05:30
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-09-28 01:51:47 +02:00
|
|
|
if err = element.Click(proto.InputMouseButtonLeft, 1); err != nil {
|
2021-02-21 16:31:34 +05:30
|
|
|
return errors.Wrap(err, "could not click element")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// KeyboardAction executes a keyboard action on the page.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) KeyboardAction(act *Action, out map[string]string) error {
|
2022-06-22 00:31:08 +05:30
|
|
|
return p.page.Keyboard.Type([]input.Key(p.getActionArgWithDefaultValues(act, "keys"))...)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RightClickElement executes right click actions for an element.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) RightClickElement(act *Action, out map[string]string) error {
|
2021-02-21 16:31:34 +05:30
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-09-28 01:51:47 +02:00
|
|
|
if err = element.Click(proto.InputMouseButtonRight, 1); err != nil {
|
2021-02-21 16:31:34 +05:30
|
|
|
return errors.Wrap(err, "could not right click element")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Screenshot executes screenshot action on a page
|
|
|
|
|
func (p *Page) Screenshot(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
to := p.getActionArgWithDefaultValues(act, "to")
|
2021-02-21 16:31:34 +05:30
|
|
|
if to == "" {
|
|
|
|
|
to = ksuid.New().String()
|
2021-03-06 14:31:21 +05:30
|
|
|
if act.Name != "" {
|
|
|
|
|
out[act.Name] = to
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
var data []byte
|
|
|
|
|
var err error
|
2022-01-31 02:25:14 +01:00
|
|
|
if p.getActionArgWithDefaultValues(act, "fullpage") == "true" {
|
2021-02-21 16:31:34 +05:30
|
|
|
data, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})
|
|
|
|
|
} else {
|
|
|
|
|
data, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not take screenshot")
|
|
|
|
|
}
|
2024-01-05 03:23:08 +05:30
|
|
|
targetPath := p.getActionArgWithDefaultValues(act, "to")
|
|
|
|
|
targetPath, err = fileutil.CleanPath(targetPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errorutil.New("could not clean output screenshot path %s", targetPath)
|
|
|
|
|
}
|
|
|
|
|
// allow if targetPath is child of current working directory
|
|
|
|
|
if !protocolstate.IsLFAAllowed() {
|
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errorutil.NewWithErr(err).Msgf("could not get current working directory")
|
|
|
|
|
}
|
|
|
|
|
if !strings.HasPrefix(targetPath, cwd) {
|
|
|
|
|
// writing outside of cwd requires -lfa flag
|
|
|
|
|
return ErrLFAccessDenied
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// edgecase create directory if mkdir=true and path contains directory
|
2023-03-24 00:44:32 +05:30
|
|
|
if p.getActionArgWithDefaultValues(act, "mkdir") == "true" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {
|
|
|
|
|
// creates new directory if needed based on path `to`
|
|
|
|
|
// 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 {
|
|
|
|
|
return errorutil.NewWithErr(err).Msgf("failed to create directory while writing screenshot")
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-05 03:23:08 +05:30
|
|
|
|
|
|
|
|
// actual file path to write
|
|
|
|
|
filePath := targetPath
|
|
|
|
|
if !strings.HasSuffix(filePath, ".png") {
|
2023-03-24 00:44:32 +05:30
|
|
|
filePath += ".png"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if fileutil.FileExists(filePath) {
|
|
|
|
|
// return custom error as overwriting files is not supported
|
|
|
|
|
return errorutil.NewWithTag("screenshot", "failed to write screenshot, file %v already exists", filePath)
|
|
|
|
|
}
|
|
|
|
|
err = os.WriteFile(filePath, data, 0540)
|
2021-02-21 16:31:34 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not write screenshot")
|
|
|
|
|
}
|
2023-03-24 00:44:32 +05:30
|
|
|
gologger.Info().Msgf("Screenshot successfully saved at %v\n", filePath)
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InputElement executes input element actions for an element.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) InputElement(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
value := p.getActionArgWithDefaultValues(act, "value")
|
2021-02-21 16:31:34 +05:30
|
|
|
if value == "" {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errinvalidArguments
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.Input(value); err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not input element")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TimeInputElement executes time input on an element
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) TimeInputElement(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
value := p.getActionArgWithDefaultValues(act, "value")
|
2021-02-21 16:31:34 +05:30
|
|
|
if value == "" {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errinvalidArguments
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
t, err := time.Parse(time.RFC3339, value)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not parse time")
|
|
|
|
|
}
|
|
|
|
|
if err := element.InputTime(t); err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not input element")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectInputElement executes select input statement action on a element
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) SelectInputElement(act *Action, out map[string]string) error {
|
2022-01-31 02:25:14 +01:00
|
|
|
value := p.getActionArgWithDefaultValues(act, "value")
|
2021-02-21 16:31:34 +05:30
|
|
|
if value == "" {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errinvalidArguments
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
|
2021-11-25 18:54:16 +02:00
|
|
|
selectedBool := false
|
2022-01-31 02:25:14 +01:00
|
|
|
if p.getActionArgWithDefaultValues(act, "selected") == "true" {
|
2021-11-25 18:54:16 +02:00
|
|
|
selectedBool = true
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-01-31 02:25:14 +01:00
|
|
|
by := p.getActionArgWithDefaultValues(act, "selector")
|
2021-11-25 18:54:16 +02:00
|
|
|
if err := element.Select([]string{value}, selectedBool, selectorBy(by)); err != nil {
|
2021-02-21 16:31:34 +05:30
|
|
|
return errors.Wrap(err, "could not select input")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WaitLoad waits for the page to load
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) WaitLoad(act *Action, out map[string]string) error {
|
2022-11-23 18:21:22 +05:30
|
|
|
p.page.Timeout(2 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
|
2021-02-21 16:31:34 +05:30
|
|
|
|
|
|
|
|
// Wait for the window.onload event and also wait for the network requests
|
2022-06-01 20:30:18 +02:00
|
|
|
// to become idle for a maximum duration of 3 seconds. If the requests
|
2021-02-21 16:31:34 +05:30
|
|
|
// do not finish,
|
|
|
|
|
if err := p.page.WaitLoad(); err != nil {
|
2022-06-01 20:30:18 +02:00
|
|
|
return errors.Wrap(err, "could not wait load event")
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
_ = p.page.WaitIdle(1 * time.Second)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetResource gets a resource from an element from page.
|
|
|
|
|
func (p *Page) GetResource(act *Action, out map[string]string) error {
|
|
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
resource, err := element.Resource()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not get src for element")
|
|
|
|
|
}
|
2021-03-06 14:31:21 +05:30
|
|
|
if act.Name != "" {
|
|
|
|
|
out[act.Name] = string(resource)
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FilesInput acts with a file input element on page
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) FilesInput(act *Action, out map[string]string) error {
|
2021-02-21 16:31:34 +05:30
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-01-31 02:25:14 +01:00
|
|
|
value := p.getActionArgWithDefaultValues(act, "value")
|
2021-02-21 16:31:34 +05:30
|
|
|
filesPaths := strings.Split(value, ",")
|
|
|
|
|
if err := element.SetFiles(filesPaths); err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not set files")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExtractElement extracts from an element on the page.
|
|
|
|
|
func (p *Page) ExtractElement(act *Action, out map[string]string) error {
|
|
|
|
|
element, err := p.pageElementBy(act.Data)
|
|
|
|
|
if err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotGetElement)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
if err = element.ScrollIntoView(); err != nil {
|
2023-01-05 12:56:18 +01:00
|
|
|
return errors.Wrap(err, errCouldNotScroll)
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
2022-01-31 02:25:14 +01:00
|
|
|
switch p.getActionArgWithDefaultValues(act, "target") {
|
2021-02-21 16:31:34 +05:30
|
|
|
case "attribute":
|
2022-01-31 02:25:14 +01:00
|
|
|
attrName := p.getActionArgWithDefaultValues(act, "attribute")
|
2021-02-21 16:31:34 +05:30
|
|
|
if attrName == "" {
|
|
|
|
|
return errors.New("attribute can't be empty")
|
|
|
|
|
}
|
|
|
|
|
attrValue, err := element.Attribute(attrName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not get attribute")
|
|
|
|
|
}
|
2021-03-06 14:31:21 +05:30
|
|
|
if act.Name != "" {
|
|
|
|
|
out[act.Name] = *attrValue
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
default:
|
|
|
|
|
text, err := element.Text()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "could not get element text node")
|
|
|
|
|
}
|
2021-03-06 14:31:21 +05:30
|
|
|
if act.Name != "" {
|
|
|
|
|
out[act.Name] = text
|
|
|
|
|
}
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WaitEvent waits for an event to happen on the page.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) WaitEvent(act *Action, out map[string]string) (func() error, error) {
|
2022-01-31 02:25:14 +01:00
|
|
|
event := p.getActionArgWithDefaultValues(act, "event")
|
2021-02-21 16:31:34 +05:30
|
|
|
if event == "" {
|
2023-12-06 19:08:26 +05:30
|
|
|
return nil, errors.New("event not recognized")
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
|
2023-12-06 19:08:26 +05:30
|
|
|
var waitEvent proto.Event
|
|
|
|
|
gotType := proto.GetType(event)
|
|
|
|
|
if gotType == nil {
|
|
|
|
|
return nil, errorutil.New("event %v does not exist", event)
|
|
|
|
|
}
|
|
|
|
|
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, errorutil.New("event %v is not a page event", event)
|
|
|
|
|
}
|
|
|
|
|
waitEvent = tmp
|
|
|
|
|
maxDuration := 10 * time.Second // 10 sec is max wait duration for any event
|
|
|
|
|
|
|
|
|
|
// allow user to specify max-duration for wait-event
|
|
|
|
|
if value := p.getActionArgWithDefaultValues(act, "max-duration"); value != "" {
|
|
|
|
|
var err error
|
|
|
|
|
maxDuration, err = time.ParseDuration(value)
|
2021-02-21 16:31:34 +05:30
|
|
|
if err != nil {
|
2023-12-06 19:08:26 +05:30
|
|
|
return nil, errorutil.NewWithErr(err).Msgf("could not parse max-duration")
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
}
|
2023-12-06 19:08:26 +05:30
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
// Just wait the event to happen
|
2023-12-06 19:08:26 +05:30
|
|
|
waitFunc := func() (err error) {
|
|
|
|
|
// execute actual wait event
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), maxDuration)
|
|
|
|
|
defer cancel()
|
|
|
|
|
err = contextutil.ExecFunc(ctx, p.page.WaitEvent(waitEvent))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return waitFunc, nil
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pageElementBy returns a page element from a variety of inputs.
|
|
|
|
|
//
|
|
|
|
|
// Supported values for by: r -> selector & regex, x -> xpath, js -> eval js,
|
|
|
|
|
// search => query, default ("") => selector.
|
|
|
|
|
func (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {
|
|
|
|
|
by, ok := data["by"]
|
|
|
|
|
if !ok {
|
|
|
|
|
by = ""
|
|
|
|
|
}
|
|
|
|
|
page := p.page
|
|
|
|
|
|
|
|
|
|
switch by {
|
2021-02-25 12:37:47 +05:30
|
|
|
case "r", "regex":
|
2021-02-21 16:31:34 +05:30
|
|
|
return page.ElementR(data["selector"], data["regex"])
|
|
|
|
|
case "x", "xpath":
|
|
|
|
|
return page.ElementX(data["xpath"])
|
|
|
|
|
case "js":
|
|
|
|
|
return page.ElementByJS(&rod.EvalOptions{JS: data["js"]})
|
|
|
|
|
case "search":
|
2021-09-18 15:38:15 +05:30
|
|
|
elms, err := page.Search(data["query"])
|
2021-02-21 16:31:34 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-09-18 15:38:15 +05:30
|
|
|
|
|
|
|
|
if elms.First != nil {
|
|
|
|
|
return elms.First, nil
|
2021-02-21 16:31:34 +05:30
|
|
|
}
|
|
|
|
|
return nil, errors.New("no such element")
|
|
|
|
|
default:
|
|
|
|
|
return page.Element(data["selector"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-01 14:20:56 +05:30
|
|
|
// DebugAction enables debug action on a page.
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) DebugAction(act *Action, out map[string]string) error {
|
2021-03-01 14:20:56 +05:30
|
|
|
p.instance.browser.engine.SlowMotion(5 * time.Second)
|
|
|
|
|
p.instance.browser.engine.Trace(true)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SleepAction sleeps on the page for a specified duration
|
2024-04-03 17:50:57 +02:00
|
|
|
func (p *Page) SleepAction(act *Action, out map[string]string) error {
|
2021-03-01 14:20:56 +05:30
|
|
|
seconds := act.Data["duration"]
|
|
|
|
|
if seconds == "" {
|
|
|
|
|
seconds = "5"
|
|
|
|
|
}
|
|
|
|
|
parsed, err := strconv.Atoi(seconds)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(time.Duration(parsed) * time.Second)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:31:34 +05:30
|
|
|
// selectorBy returns a selector from a representation.
|
|
|
|
|
func selectorBy(selector string) rod.SelectorType {
|
|
|
|
|
switch selector {
|
|
|
|
|
case "r":
|
|
|
|
|
return rod.SelectorTypeRegex
|
|
|
|
|
case "css":
|
|
|
|
|
return rod.SelectorTypeCSSSector
|
|
|
|
|
case "regex":
|
|
|
|
|
return rod.SelectorTypeRegex
|
|
|
|
|
default:
|
|
|
|
|
return rod.SelectorTypeText
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 02:25:14 +01:00
|
|
|
func (p *Page) getActionArgWithDefaultValues(action *Action, arg string) string {
|
2022-02-04 11:43:42 +01:00
|
|
|
return p.getActionArgWithValues(action, arg, generators.MergeMaps(
|
|
|
|
|
generators.BuildPayloadFromOptions(p.instance.browser.options),
|
|
|
|
|
p.payloads,
|
|
|
|
|
))
|
2022-01-31 02:25:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Page) getActionArgWithValues(action *Action, arg string, values map[string]interface{}) string {
|
|
|
|
|
argValue := action.GetArg(arg)
|
2022-01-31 08:52:36 +01:00
|
|
|
argValue = replaceWithValues(argValue, values)
|
|
|
|
|
if p.instance.interactsh != nil {
|
|
|
|
|
var interactshURLs []string
|
2023-02-07 09:32:10 +01:00
|
|
|
argValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)
|
2022-01-31 08:52:36 +01:00
|
|
|
p.addInteractshURL(interactshURLs...)
|
|
|
|
|
}
|
|
|
|
|
return argValue
|
2022-01-31 02:25:14 +01:00
|
|
|
}
|