mirror of https://github.com/daffainfo/nuclei.git
headless: fix panic + refactor waitevent action (#4465)
* fix waitEvent action * avoid future panics * integration test + bug fix * headless: add max-duration support in waitevent * fix comment + max-duration inputdev
parent
7da3921c12
commit
47e75038f0
|
@ -12,6 +12,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-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{}},
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
id: headless-waitevent
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: WaitEvent
|
||||||
|
severity: info
|
||||||
|
author: pdteam
|
||||||
|
|
||||||
|
headless:
|
||||||
|
- steps:
|
||||||
|
# note waitevent must be used before navigating to any page
|
||||||
|
# unlike waitload
|
||||||
|
- action: waitevent
|
||||||
|
args:
|
||||||
|
event: 'Page.loadEventFired'
|
||||||
|
max-duration: 15s
|
||||||
|
|
||||||
|
- action: navigate
|
||||||
|
args:
|
||||||
|
url: "{{BaseURL}}/"
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- "<html>"
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -21,6 +22,7 @@ import (
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
||||||
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
||||||
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
||||||
|
contextutil "github.com/projectdiscovery/utils/context"
|
||||||
errorutil "github.com/projectdiscovery/utils/errors"
|
errorutil "github.com/projectdiscovery/utils/errors"
|
||||||
fileutil "github.com/projectdiscovery/utils/file"
|
fileutil "github.com/projectdiscovery/utils/file"
|
||||||
folderutil "github.com/projectdiscovery/utils/folder"
|
folderutil "github.com/projectdiscovery/utils/folder"
|
||||||
|
@ -41,13 +43,33 @@ 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{}) (map[string]string, error) {
|
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (outData map[string]string, err error) {
|
||||||
outData := make(map[string]string)
|
outData = make(map[string]string)
|
||||||
var err error
|
// 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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
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, variables)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
case ActionScript:
|
case ActionScript:
|
||||||
err = p.RunScript(act, outData)
|
err = p.RunScript(act, outData)
|
||||||
case ActionClick:
|
case ActionClick:
|
||||||
|
@ -69,7 +91,11 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, var
|
||||||
case ActionExtract:
|
case ActionExtract:
|
||||||
err = p.ExtractElement(act, outData)
|
err = p.ExtractElement(act, outData)
|
||||||
case ActionWaitEvent:
|
case ActionWaitEvent:
|
||||||
err = p.WaitEvent(act, outData)
|
var waitFunc func() error
|
||||||
|
waitFunc, err = p.WaitEvent(act, outData)
|
||||||
|
if waitFunc != nil {
|
||||||
|
waitFuncs = append(waitFuncs, waitFunc)
|
||||||
|
}
|
||||||
case ActionFilesInput:
|
case ActionFilesInput:
|
||||||
if p.options.Options.AllowLocalFileAccess {
|
if p.options.Options.AllowLocalFileAccess {
|
||||||
err = p.FilesInput(act, outData)
|
err = p.FilesInput(act, outData)
|
||||||
|
@ -541,38 +567,43 @@ func (p *Page) ExtractElement(act *Action, out map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type protoEvent struct {
|
|
||||||
event string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProtoEvent returns the cdp.Event.Method
|
|
||||||
func (p *protoEvent) ProtoEvent() string {
|
|
||||||
return p.event
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 map[string]string /*TODO review unused parameter*/) error {
|
func (p *Page) WaitEvent(act *Action, out map[string]string /*TODO review unused parameter*/) (func() error, error) {
|
||||||
event := p.getActionArgWithDefaultValues(act, "event")
|
event := p.getActionArgWithDefaultValues(act, "event")
|
||||||
if event == "" {
|
if event == "" {
|
||||||
return errors.New("event not recognized")
|
return nil, errors.New("event not recognized")
|
||||||
}
|
}
|
||||||
protoEvent := &protoEvent{event: event}
|
|
||||||
|
|
||||||
// Uses another instance in order to be able to chain the timeout only to the wait operation
|
var waitEvent proto.Event
|
||||||
pageCopy := p.page
|
gotType := proto.GetType(event)
|
||||||
timeout := p.getActionArg(act, "timeout")
|
if gotType == nil {
|
||||||
if timeout != "" {
|
return nil, errorutil.New("event %v does not exist", event)
|
||||||
ts, err := strconv.Atoi(timeout)
|
}
|
||||||
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not get timeout")
|
return nil, errorutil.NewWithErr(err).Msgf("could not parse max-duration")
|
||||||
}
|
|
||||||
if ts > 0 {
|
|
||||||
pageCopy = p.page.Timeout(time.Duration(ts) * time.Second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just wait the event to happen
|
// Just wait the event to happen
|
||||||
pageCopy.WaitEvent(protoEvent)()
|
waitFunc := func() (err error) {
|
||||||
return nil
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageElementBy returns a page element from a variety of inputs.
|
// pageElementBy returns a page element from a variety of inputs.
|
||||||
|
@ -643,10 +674,6 @@ func selectorBy(selector string) rod.SelectorType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) getActionArg(action *Action, arg string) string {
|
|
||||||
return p.getActionArgWithValues(action, arg, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Page) getActionArgWithDefaultValues(action *Action, arg string) string {
|
func (p *Page) getActionArgWithDefaultValues(action *Action, arg string) string {
|
||||||
return p.getActionArgWithValues(action, arg, generators.MergeMaps(
|
return p.getActionArgWithValues(action, arg, generators.MergeMaps(
|
||||||
generators.BuildPayloadFromOptions(p.instance.browser.options),
|
generators.BuildPayloadFromOptions(p.instance.browser.options),
|
||||||
|
|
Loading…
Reference in New Issue