Compare commits

...
Author SHA1 Message Date
Ice3man 4626b56dd0 bugfix: fixed issues related to linear output + misc 2024-09-13 23:39:39 +05:30
Ice3man d946a7890a feat: fixed unmarshal issues 2024-09-05 15:46:11 +05:30
Ice3man e941b685e0 misc 2024-09-05 01:06:52 +05:30
Ice3man 55639e32c8 feat: added linear issue tracker support to nuclei 2024-09-05 01:05:24 +05:30
sandeep 042b33de3d version update 2024-09-02 17:38:48 +05:30
dependabot[bot] 5bb730849c chore(deps): bump github.com/projectdiscovery/httpx from 1.6.7 to 1.6.8 (#5590)
Bumps [github.com/projectdiscovery/httpx](https://github.com/projectdiscovery/httpx) from 1.6.7 to 1.6.8.
- [Release notes](https://github.com/projectdiscovery/httpx/releases)
- [Changelog](https://github.com/projectdiscovery/httpx/blob/main/.goreleaser.yml)
- [Commits](https://github.com/projectdiscovery/httpx/compare/v1.6.7...v1.6.8)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/httpx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:55:34 +05:30
dependabot[bot] 7bc257d7a5 chore(deps): bump github.com/projectdiscovery/ratelimit (#5588)
Bumps [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) from 0.0.49 to 0.0.53.
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.49...v0.0.53)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:47:35 +05:30
dependabot[bot] 04a6159713 chore(deps): bump github.com/projectdiscovery/retryablehttp-go (#5591)
Bumps [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) from 1.0.74 to 1.0.76.
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.0.74...v1.0.76)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:46:16 +05:30
dependabot[bot] 0c4c0af4e9 chore(deps): bump github.com/projectdiscovery/dsl from 0.1.8 to 0.2.1 (#5587)
Bumps [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) from 0.1.8 to 0.2.1.
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.1.8...v0.2.1)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/dsl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:36:39 +05:30
ghost cb05f55251 Auto Generate Syntax Docs + JSONSchema [Mon Sep 2 10:01:02 UTC 2024] 🤖 2024-09-02 10:01:02 +00:00
Dwi Siswanto 841d8913e5 feat(headless): add ActionWaitDialog type (#5545)
* feat(headless): add `dialog` action type

also implement it

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor(headless): add `ActionData` for action output datas

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor(headless): rm `value` arg for `*Page.HandleDialog`

also:
* expose `err` from \*proto.PageHandleJavaScriptDialog`
* conditional ActionData assignment based on

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor(headless): rename to `ActionWaitDialog`

Signed-off-by: Dwi Siswanto <[email protected]>

* test(headless): fix mismatch assertion of `src` output of `ActionGetResource`

Signed-off-by: Dwi Siswanto <[email protected]>

* test(headless): add TestActionWaitDialog test case

Signed-off-by: Dwi Siswanto <[email protected]>

* feat(headless): add `GetActionDataWithDefault` generic func

Signed-off-by: Dwi Siswanto <[email protected]>

* feat(headless): implement `GetActionDataWithDefault`

to `header` & `status_code`

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor(headless): use `mapsutil.Map` instead

Signed-off-by: Dwi Siswanto <[email protected]>

* Revert "feat(headless): add `GetActionDataWithDefault` generic func"

This reverts commit fa12e0d6a2.

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2024-09-02 15:29:52 +05:30
dependabot[bot] 9a5272985c Merge pull request #5589 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/fastdialer-0.2.7 2024-09-02 05:38:41 +00:00
dependabot[bot] fdb8e0a8c6 chore(deps): bump github.com/projectdiscovery/fastdialer
Bumps [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) from 0.2.5 to 0.2.7.
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.2.5...v0.2.7)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-09-02 05:11:44 +00:00
linchizhen 88b281ae47 chore: fix some function names in comment (#5586)
Signed-off-by: linchizhen <[email protected]>
2024-09-01 14:11:01 +05:30
Dwi Siswanto e0b2542868 feat: conditionally panic-recover (#5553)
* feat: conditionally panic-recover

As discussed with @Mzack9999, we should avoid
overusing panic-recover. We need to review the RCA
first to determine whether this is an exceptional
situation or if it's a higher-level function meant
to recover from a panic. This approach will help
us establish a robust error-handling strategy.

The implementation of panic-recover should be
conditional and NOT applied when running in a CI
environment AND IS temporary. Once we've caught
all errors and made the necessary corrections, we
can remove the deferred recover function.

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(deps): bump `go-ci` to v1.0.2

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(make): add `-race` to `GOFLAGS` in `test`

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2024-08-28 17:57:45 +05:30
Dogan Can Bakir 6b71af448a Fixed issue with -ms option to scan non accessible host (#5576)
* fail if OnResult callback is not called

* generate error message from error logs

* try..parse..

* fix lint

* add error message to last matcher event

* fix network protocol error logging

* log returned log from ExecuteWithResults

* add back specific logging

* clean up the msg

* minor

* init integration test for -ms

* add tests for http,network,js,ws protocols

* fix lint

* fix network test

* return err for dns protocol

* add integration test for dns protocol
2024-08-28 16:27:43 +05:30
Peter Dave Hello bf58b4d756 Remove conflicting -U option for apk in Dockerfile (#5578)
The `--no-cache` option updates the index and avoids temporary files, making `-U` unnecessary, consistent with the subsequent `apk add`.
2024-08-28 16:24:15 +05:30
Dwi Siswanto aac1af1308 refactor(fuzz): use mapsutil.Map type (#5558)
* refactor(fuzz): use `mapsutil.Map` type

Signed-off-by: Dwi Siswanto <[email protected]>

* fix(headless): handle empty `key` in `*Value.SetParsedValue`

Signed-off-by: Dwi Siswanto <[email protected]>

* feat(fuzz): add type assertion checks

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2024-08-28 12:41:02 +05:30
Dogan Can Bakir 4a85e73fbb skip dependabot prs (#5575) 2024-08-26 22:18:45 +05:30
dependabot[bot] c23e1864d6 Merge pull request #5574 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/tlsx-1.1.7 2024-08-26 06:21:12 +00:00
dependabot[bot] 2670b6865f chore(deps): bump github.com/projectdiscovery/tlsx from 1.1.6 to 1.1.7
Bumps [github.com/projectdiscovery/tlsx](https://github.com/projectdiscovery/tlsx) from 1.1.6 to 1.1.7.
- [Release notes](https://github.com/projectdiscovery/tlsx/releases)
- [Changelog](https://github.com/projectdiscovery/tlsx/blob/main/.goreleaser.yml)
- [Commits](https://github.com/projectdiscovery/tlsx/compare/v1.1.6...v1.1.7)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/tlsx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-26 05:53:48 +00:00
dependabot[bot] b424ba9f50 Merge pull request #5570 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/goflags-0.1.64 2024-08-26 05:53:42 +00:00
dependabot[bot] faed7798fc Merge pull request #5571 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/utils-0.2.7 2024-08-26 05:52:06 +00:00
dependabot[bot] 253fbc3619 chore(deps): bump github.com/projectdiscovery/goflags
Bumps [github.com/projectdiscovery/goflags](https://github.com/projectdiscovery/goflags) from 0.1.63 to 0.1.64.
- [Release notes](https://github.com/projectdiscovery/goflags/releases)
- [Commits](https://github.com/projectdiscovery/goflags/compare/v0.1.63...v0.1.64)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/goflags
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-26 05:26:23 +00:00
dependabot[bot] 4a39fdb8d6 chore(deps): bump github.com/projectdiscovery/utils from 0.2.5 to 0.2.7
Bumps [github.com/projectdiscovery/utils](https://github.com/projectdiscovery/utils) from 0.2.5 to 0.2.7.
- [Release notes](https://github.com/projectdiscovery/utils/releases)
- [Changelog](https://github.com/projectdiscovery/utils/blob/main/CHANGELOG.md)
- [Commits](https://github.com/projectdiscovery/utils/compare/v0.2.5...v0.2.7)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/utils
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-26 05:26:22 +00:00
dependabot[bot] 5627da8c19 Merge pull request #5573 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/gologger-1.1.21 2024-08-26 05:25:22 +00:00
dependabot[bot] 710f1ad240 chore(deps): bump github.com/projectdiscovery/gologger
Bumps [github.com/projectdiscovery/gologger](https://github.com/projectdiscovery/gologger) from 1.1.19 to 1.1.21.
- [Release notes](https://github.com/projectdiscovery/gologger/releases)
- [Commits](https://github.com/projectdiscovery/gologger/compare/v1.1.19...v1.1.21)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/gologger
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-26 05:02:29 +00:00
34 changed files with 1205 additions and 108 deletions

No files matched your search

+2 -3
View File
@@ -3,6 +3,5 @@ reviewers:
- dogancanbakir
numberOfReviewers: 1
# A list of keywords to be skipped the process that add reviewers if pull requests include it
# skipKeywords:
# - wip
skipKeywords:
- '@dependabot'
+1 -1
View File
@@ -9,7 +9,7 @@ RUN go build ./cmd/nuclei
# Release
FROM alpine:3.18.6
RUN apk -U upgrade --no-cache \
RUN apk upgrade --no-cache \
&& apk add --no-cache bind-tools chromium ca-certificates
COPY --from=build-env /app/nuclei /usr/local/bin/
+1
View File
@@ -65,6 +65,7 @@ docs:
git reset --hard # line 59
test: GOFLAGS = -race -v
test:
$(GOTEST) $(GOFLAGS) ./...
+2
View File
@@ -3682,6 +3682,8 @@ Enum Values:
- <code>waitevent</code>
- <code>dialog</code>
- <code>keyboard</code>
- <code>debug</code>
+1
View File
@@ -55,6 +55,7 @@ var (
"dsl": dslTestcases,
"flow": flowTestcases,
"javascript": jsTestcases,
"matcher-status": matcherStatusTestcases,
}
// flakyTests are run with a retry count of 3
flakyTests = map[string]bool{
+119
View File
@@ -0,0 +1,119 @@
package main
import (
"encoding/json"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var matcherStatusTestcases = []TestCaseInfo{
{Path: "protocols/http/get.yaml", TestCase: &httpNoAccess{}},
{Path: "protocols/network/net-https.yaml", TestCase: &networkNoAccess{}},
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessNoAccess{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNoAccess{}},
{Path: "protocols/websocket/basic.yaml", TestCase: &websocketNoAccess{}},
{Path: "protocols/dns/a.yaml", TestCase: &dnsNoAccess{}},
}
type httpNoAccess struct{}
func (h *httpNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error != "no address found for host" {
return fmt.Errorf("unexpected result: expecting \"no address found for host\" error but got none")
}
return nil
}
type networkNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error != "no address found for host" {
return fmt.Errorf("unexpected result: expecting \"no address found for host\" error but got \"%s\"", event.Error)
}
return nil
}
type headlessNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-headless", "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type javascriptNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *javascriptNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type websocketNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "ws://trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type dnsNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
+1 -1
View File
@@ -300,7 +300,7 @@ func formatTemplate(data string) (string, bool, error) {
return data, false, errorutil.New("template format failed")
}
// lintTemplateData lints template data using templateman lint api
// lintTemplate lints template data using templateman lint api
func lintTemplate(data string) (bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/lint", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
+14 -12
View File
@@ -20,12 +20,12 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.1.0
github.com/projectdiscovery/fastdialer v0.2.5
github.com/projectdiscovery/hmap v0.0.55
github.com/projectdiscovery/fastdialer v0.2.7
github.com/projectdiscovery/hmap v0.0.56
github.com/projectdiscovery/interactsh v1.2.0
github.com/projectdiscovery/rawhttp v0.1.60
github.com/projectdiscovery/retryabledns v1.0.72
github.com/projectdiscovery/retryablehttp-go v1.0.74
github.com/projectdiscovery/rawhttp v0.1.61
github.com/projectdiscovery/retryabledns v1.0.73
github.com/projectdiscovery/retryablehttp-go v1.0.76
github.com/projectdiscovery/yamldoc-go v1.0.4
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.5.0
@@ -72,6 +72,7 @@ require (
github.com/go-sql-driver/mysql v1.7.1
github.com/h2non/filetype v1.1.3
github.com/invopop/yaml v0.3.1
github.com/kitabisa/go-ci v1.0.2
github.com/labstack/echo/v4 v4.10.2
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.10.9
@@ -80,26 +81,27 @@ require (
github.com/microsoft/go-mssqldb v1.6.0
github.com/ory/dockertest/v3 v3.10.0
github.com/praetorian-inc/fingerprintx v1.1.9
github.com/projectdiscovery/dsl v0.1.8
github.com/projectdiscovery/dsl v0.2.1
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
github.com/projectdiscovery/goflags v0.1.63
github.com/projectdiscovery/gologger v1.1.19
github.com/projectdiscovery/goflags v0.1.64
github.com/projectdiscovery/gologger v1.1.21
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.0.2
github.com/projectdiscovery/httpx v1.6.7
github.com/projectdiscovery/httpx v1.6.8
github.com/projectdiscovery/mapcidr v1.1.34
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/ratelimit v0.0.49
github.com/projectdiscovery/ratelimit v0.0.53
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/tlsx v1.1.7
github.com/projectdiscovery/uncover v1.0.9
github.com/projectdiscovery/useragent v0.0.65
github.com/projectdiscovery/utils v0.2.5
github.com/projectdiscovery/utils v0.2.7
github.com/projectdiscovery/wappalyzergo v0.1.14
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
github.com/stretchr/testify v1.9.0
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706
+28 -24
View File
@@ -625,6 +625,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kitabisa/go-ci v1.0.2 h1:rqHf8KEbQOxVb998TbqGRo70Z7ol44io7/jLYJUvKp8=
github.com/kitabisa/go-ci v1.0.2/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
@@ -830,28 +832,28 @@ github.com/projectdiscovery/cdncheck v1.1.0 h1:qDITidmJsejzpk3rMkauCh6sjI2GH9hW/
github.com/projectdiscovery/cdncheck v1.1.0/go.mod h1:sZ8U4MjHSsyaTVjBbYWHT1cwUVvUYwDX1W+WvWRicIc=
github.com/projectdiscovery/clistats v0.1.0 h1:b+LF1w0xhNd7cneKWMXb+/yUmF1n5szawxP4XGpmbxs=
github.com/projectdiscovery/clistats v0.1.0/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
github.com/projectdiscovery/dsl v0.1.8 h1:ulz+o097XsYgqP4QXaAQhVSkoeD2ZPWd29wX3CTodUA=
github.com/projectdiscovery/dsl v0.1.8/go.mod h1:AYJS2WQ/q0smr2v4pEJTg4DPe6k56KFKR7UFXvzNz/4=
github.com/projectdiscovery/fastdialer v0.2.5 h1:OZg31LFIBBHbXVnOEcFNtD0gBM80zpY68QvYI0xbw80=
github.com/projectdiscovery/fastdialer v0.2.5/go.mod h1:E1vmos7AeD/KZ7rsDeImjLp5H3jjas+2utVdUxGulvY=
github.com/projectdiscovery/dsl v0.2.1 h1:TK3KD4jsg4YbvY7WJqnz1QyH4AOvAwezeBFOX97Evgk=
github.com/projectdiscovery/dsl v0.2.1/go.mod h1:IRQXsmi5/g1dDZ79//A9t2vrRtxm4frRSd5t8CZVSbI=
github.com/projectdiscovery/fastdialer v0.2.7 h1:x7bEf/kDyVOuG9kHfJLMhWO3h7J+6fMDgLWkGYL8EFg=
github.com/projectdiscovery/fastdialer v0.2.7/go.mod h1:7/BGdaw7sCekuBh6lLZqPuoO5N0Qaizwvex/PjO+V8o=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
github.com/projectdiscovery/goflags v0.1.63 h1:xy3nqpzmSFEmG0LKTv3xLcp9LMbvbFkLZcjcnFe3xR4=
github.com/projectdiscovery/goflags v0.1.63/go.mod h1:ptFbCm+5RmSHTSG4oniwBRRHJlguBTOOfMUfezeGbZ0=
github.com/projectdiscovery/gologger v1.1.19 h1:b7cU32XuDrDiwhr7hlDeE6mfj/nENBtHEohe51txJCE=
github.com/projectdiscovery/gologger v1.1.19/go.mod h1:DbeKwx9IEfcvnclImX5gBlhIKUuOZwOM5itdpYXl+54=
github.com/projectdiscovery/goflags v0.1.64 h1:FDfwdt9N97Hi8OuhbkDlKtVttpc/CRMIWQVa08VsHsI=
github.com/projectdiscovery/goflags v0.1.64/go.mod h1:3FyHIVQtnycNOc1LE3O1jj/XR5XuMdF9QfHd0ujhnX4=
github.com/projectdiscovery/gologger v1.1.21 h1:FD9MGE9adY/y2IxDpQgPZIvZAkQIx3bvcuY+d1FLQUo=
github.com/projectdiscovery/gologger v1.1.21/go.mod h1:eyvlcHiSuzJQtwmh+fcfwZVaqVgSGtEP1oL1gJFgP3c=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
github.com/projectdiscovery/gozero v0.0.2 h1:8fJeaCjxL9tpm33uG/RsCQs6HGM/NE6eA3cjkilRQ+E=
github.com/projectdiscovery/gozero v0.0.2/go.mod h1:d8bZvDWW07LWNYWrwjZ4OO1I0cpkfqaysyDfSs9ibK8=
github.com/projectdiscovery/hmap v0.0.55 h1:By0qSeQRE/ybeITUEL6oURN+j7qX7FVjCq92duwsqcA=
github.com/projectdiscovery/hmap v0.0.55/go.mod h1:uZOF/W8yOGDb/t9omqATirhVwiboa5F/c00OVhDeE0E=
github.com/projectdiscovery/httpx v1.6.7 h1:luJAtQ+iKpiyKKDpcWw1/fF1MaRX6JZ4R3P+ol5Uikk=
github.com/projectdiscovery/httpx v1.6.7/go.mod h1:rqcuexBrb4v36ZyX0BHcHaovTurOJn2P65Tdt9cYdfE=
github.com/projectdiscovery/hmap v0.0.56 h1:QLIuFhCc7jrBH9WYpxNBDooSoH+/S8cCm7VO5rMt3lU=
github.com/projectdiscovery/hmap v0.0.56/go.mod h1:XyGKMO93nLOzpaTTCDBSKEGx/XWXjSpoyZJxT92Qd/Q=
github.com/projectdiscovery/httpx v1.6.8 h1:k0Y5g3ue/7QbDP0+LykIxp/VhPDLfau3UEUyuxtP7qE=
github.com/projectdiscovery/httpx v1.6.8/go.mod h1:7BIsDxyRwkBjthqFmEajXrA5f3yb4tlVfLmpNdf0ZXA=
github.com/projectdiscovery/interactsh v1.2.0 h1:Al6jHiR+Usl9egYJDLJaWNHOcH8Rugk8gWMasc8Cmw8=
github.com/projectdiscovery/interactsh v1.2.0/go.mod h1:Wxt0fnzxsfrAZQQlpVrf3xMatP4OXZaZbjuDkIQKdYY=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
@@ -864,28 +866,28 @@ github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.0.9 h1:IrlDoYZagNNO8y+7iZeHT8k5izE+nek7TdtvEBwCxqk=
github.com/projectdiscovery/networkpolicy v0.0.9/go.mod h1:XFJ2Lnv8BE/ziQCFjBHMsH1w6VmkPiQtk+NlBpdMU7M=
github.com/projectdiscovery/ratelimit v0.0.49 h1:PYatMp8g5OuoFsZOA90e48nLd2vB6a4Tw0FZ8h9zqkQ=
github.com/projectdiscovery/ratelimit v0.0.49/go.mod h1:Xi0LTMHg4HQlmCZFzRBIhRW6N+QW5RxQ8V/Qs+Vta4k=
github.com/projectdiscovery/rawhttp v0.1.60 h1:L57kAATGRuCfOp8Fs0ly3j2wlip7R5pco2RVfwqh/II=
github.com/projectdiscovery/rawhttp v0.1.60/go.mod h1:M97w6lsphTHKeqLSio4Lw9K4KLO2riJMYAOqVMIZ3ck=
github.com/projectdiscovery/ratelimit v0.0.53 h1:1ci2zaVCtJ9daDi9IXjNwpDxl2InYq375role/xqUR4=
github.com/projectdiscovery/ratelimit v0.0.53/go.mod h1:4kNYTL7xKLo1qMYHTREUCBqHyvykFF+XBWutTh9fkwg=
github.com/projectdiscovery/rawhttp v0.1.61 h1:EbskCj6kkDSG31sO5zEUFTqHp9ltccG1DdcI+MCdahQ=
github.com/projectdiscovery/rawhttp v0.1.61/go.mod h1:5XmDAKph9pLVnh87zjL+vXDpfG5W8Gz5N2BAtYTkuVU=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gBVSorSzvmm0bFa7gDV4QNSOWPL/fgZ4kTXBxk=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg=
github.com/projectdiscovery/retryabledns v1.0.72 h1:+k9JRYBMpZIyEQZJWVHoCHx1TEdpiuAS7iEru2bhs3A=
github.com/projectdiscovery/retryabledns v1.0.72/go.mod h1:BDhYnqSH0Os2G37eb4L4vnKrUcik6Q6pR02Cq5PbT+0=
github.com/projectdiscovery/retryablehttp-go v1.0.74 h1:jXZsg3nNDuDjGNeL0+hiEx/dqUqPyZW1zsotWW1y70c=
github.com/projectdiscovery/retryablehttp-go v1.0.74/go.mod h1:fVRxa2fVMbomaPiOFA05kM2WrjV8wPFHW7J84eenVmQ=
github.com/projectdiscovery/retryabledns v1.0.73 h1:CLqtgoI6+YVcUE2jnZPFVvwj59CHDE4HNImLf62sSuw=
github.com/projectdiscovery/retryabledns v1.0.73/go.mod h1:Wo5tQHKtx6XcYYhMypRg8dr0GGnqIb4CzoTSEw2lo7I=
github.com/projectdiscovery/retryablehttp-go v1.0.76 h1:2+4QpWnXUZFBkoeCEBXtUjSeBTdgKtaGwP8LRtKAC3g=
github.com/projectdiscovery/retryablehttp-go v1.0.76/go.mod h1:0qIv7Ld8WWSKWYXMsNAWRYU5gsxnxpBza3SjDgt6nVc=
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
github.com/projectdiscovery/tlsx v1.1.6 h1:iw2zwKbd2+kRQ8J1G4dLmS0CLyemd/tKz1UzcNsC77A=
github.com/projectdiscovery/tlsx v1.1.6/go.mod h1:s7SRRFdrwIZBK/RXXZi4CR/CubqFSvp8h5Bk1srEZIo=
github.com/projectdiscovery/tlsx v1.1.7 h1:eSsl/SmTDL/z2CMeSrbssk4f/9oOotMP1SgXl3yynSM=
github.com/projectdiscovery/tlsx v1.1.7/go.mod h1:g66QQ4/y4tLVjoGbzWIv+Q6xwFzxfJbEDx86Y1dYHDM=
github.com/projectdiscovery/uncover v1.0.9 h1:s5RbkD/V4r8QcPkys4gTTqMuRSgXq0JprejqLSopN9Y=
github.com/projectdiscovery/uncover v1.0.9/go.mod h1:2PUF3SpB5QNIJ8epaB2xbRzkPaxEAWRDm3Ir2ijt81U=
github.com/projectdiscovery/useragent v0.0.65 h1:x78ZwWdqpzokOHxLITUXvq+ljkTKc19z3ILGtoV1N70=
github.com/projectdiscovery/useragent v0.0.65/go.mod h1:deOP8YLJU6SCzM8k+K8PjkcOF4Ux0spqyO4ODZGIT4A=
github.com/projectdiscovery/utils v0.2.5 h1:puiw3/gK1WTEj9ZKnW00tAS0ex+ETqbLJ8juRuejA98=
github.com/projectdiscovery/utils v0.2.5/go.mod h1:fv3WC6m/+bVT+wH+fMB+JsKviEC/UAuDSeKxWPlK3kk=
github.com/projectdiscovery/utils v0.2.7 h1:XWdz7SscL++jqsnQ9ecHzSZE0RK33tyPcnqcXw+vmKs=
github.com/projectdiscovery/utils v0.2.7/go.mod h1:N0N7tbdNFPegd9NpJ3onCPClaBrERcOIB88yww6UCF8=
github.com/projectdiscovery/wappalyzergo v0.1.14 h1:nt1IM4RUmqeymsXk4h6BsZbKDoS2hjFvPkT2GaI1rz4=
github.com/projectdiscovery/wappalyzergo v0.1.14/go.mod h1:/hzgxkBFTMe2wDbA93nFfoMjULw7/vIZ9QPSAnCgUa8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
@@ -962,6 +964,8 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+1
View File
@@ -256,6 +256,7 @@
"deleteheader",
"setbody",
"waitevent",
"waitdialog",
"keyboard",
"debug",
"sleep",
+1 -1
View File
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
Version = `v3.3.1`
Version = `v3.3.2`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
+18 -3
View File
@@ -7,6 +7,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/maps"
urlutil "github.com/projectdiscovery/utils/url"
)
@@ -81,7 +82,7 @@ func (q *Path) Delete(key string) error {
// Rebuild returns a new request with the
// component rebuilt
func (q *Path) Rebuild() (*retryablehttp.Request, error) {
originalValues := make(map[string]interface{})
originalValues := mapsutil.Map[string, any]{}
splitted := strings.Split(q.req.URL.Path, "/")
for i := range splitted {
pathTillNow := strings.Join(splitted[:i+1], "/")
@@ -95,8 +96,22 @@ func (q *Path) Rebuild() (*retryablehttp.Request, error) {
lengthSplitted := len(q.value.parsed.Map)
for i := lengthSplitted; i > 0; i-- {
key := strconv.Itoa(i)
original := originalValues[key].(string)
new := q.value.parsed.Map[key].(string)
original, ok := originalValues.GetOrDefault(key, "").(string)
if !ok {
continue
}
new, ok := q.value.parsed.Map.GetOrDefault(key, "").(string)
if !ok {
continue
}
if new == original {
// no need to replace
continue
}
originalPath = strings.Replace(originalPath, original, new, 1)
}
+11 -1
View File
@@ -73,12 +73,22 @@ func (v *Value) SetParsed(data dataformat.KV, dataFormat string) {
// SetParsedValue sets the parsed value for a key
// in the parsed map
func (v *Value) SetParsedValue(key string, value string) bool {
func (v *Value) SetParsedValue(key, value string) bool {
if key == "" {
return false
}
origValue := v.parsed.Get(key)
if origValue == nil {
v.parsed.Set(key, value)
return true
}
// TODO(dwisiswant0): I'm sure that this can be simplified because
// `dataformat.KV.*` is a type of `mapsutil.*` where the value is `any`. So,
// it looks like we won't type conversion here or even have its own methods
// inside `dataformat.KV`.
// If the value is a list, append to it
// otherwise replace it
switch v := origValue.(type) {
+1 -1
View File
@@ -13,7 +13,7 @@ import (
// if it's not important/significant (ex: json,xml) we use map
// this also allows us to iteratively implement ordered map
type KV struct {
Map map[string]interface{}
Map mapsutil.Map[string, any]
OrderedMap *mapsutil.OrderedMap[string, any]
}
+1 -1
View File
@@ -416,7 +416,7 @@ schemaLabel:
return globalParams, nil
}
// generateExampleFromSchema generates an example from a schema object
// GenerateParameterFromSecurityScheme generates an example from a schema object
func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) {
if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") {
return nil, errorutil.NewWithTag("openapi", "unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)
+1 -1
View File
@@ -121,7 +121,7 @@ func NewInputProvider(opts InputOptions) (InputProvider, error) {
}
}
// SupportedFormats returns all supported input formats of nuclei
// SupportedInputFormats returns all supported input formats of nuclei
func SupportedInputFormats() string {
return "list, " + http.SupportedFormats()
}
+7
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/dop251/goja"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
@@ -120,11 +121,17 @@ func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs,
defer cancel()
// execute the script
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return ExecuteProgram(program, args, opts)
})
if err != nil {
+8
View File
@@ -11,6 +11,7 @@ import (
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/gologger"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
@@ -84,11 +85,18 @@ func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArg
opts.Cleanup(runtime)
}
}()
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %s", r)
}
}()
// set template ctx
_ = runtime.Set("template", args.TemplateCtx)
// set args
+2 -2
View File
@@ -106,7 +106,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
}
func (request *Request) execute(input *contextargs.Context, domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error {
var err error
if vardump.EnableVarDump {
gologger.Debug().Msgf("DNS Protocol request variables: \n%s\n", vardump.DumpVariables(vars))
}
@@ -199,7 +199,7 @@ func (request *Request) execute(input *contextargs.Context, domain string, metad
}
callback(event)
return nil
return err
}
func (request *Request) parseDNSInput(host string) (string, error) {
@@ -6,11 +6,15 @@ import (
"strings"
"github.com/invopop/jsonschema"
mapsutil "github.com/projectdiscovery/utils/maps"
)
// ActionType defines the action type for a browser action
type ActionType int8
// ActionData stores the action output data
type ActionData = mapsutil.Map[string, any]
// Types to be executed by the user.
// name:ActionType
const (
@@ -68,6 +72,9 @@ const (
// ActionWaitEvent waits for a specific event.
// name:waitevent
ActionWaitEvent
// ActionWaitDialog waits for JavaScript dialog (alert, confirm, prompt, or onbeforeunload).
// name:dialog
ActionWaitDialog
// ActionKeyboard performs a keyboard action event on a page.
// name:keyboard
ActionKeyboard
@@ -104,6 +111,7 @@ var ActionStringToAction = map[string]ActionType{
"deleteheader": ActionDeleteHeader,
"setbody": ActionSetBody,
"waitevent": ActionWaitEvent,
"waitdialog": ActionWaitDialog,
"keyboard": ActionKeyboard,
"debug": ActionDebug,
"sleep": ActionSleep,
@@ -130,6 +138,7 @@ var ActionToActionString = map[ActionType]string{
ActionDeleteHeader: "deleteheader",
ActionSetBody: "setbody",
ActionWaitEvent: "waitevent",
ActionWaitDialog: "waitdialog",
ActionKeyboard: "keyboard",
ActionDebug: "debug",
ActionSleep: "sleep",
+1 -1
View File
@@ -45,7 +45,7 @@ type Options struct {
}
// 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) (map[string]string, *Page, error) {
func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (ActionData, *Page, error) {
page, err := i.engine.Page(proto.TargetCreateTarget{})
if err != nil {
return nil, nil, err
+69 -24
View File
@@ -14,6 +14,7 @@ import (
"github.com/go-rod/rod/lib/input"
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
"github.com/kitabisa/go-ci"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
@@ -47,14 +48,19 @@ const (
)
// ExecuteActions executes a list of actions on a page.
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (outData map[string]string, err error) {
outData = make(map[string]string)
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (outData ActionData, err error) {
outData = make(ActionData)
// 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
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = errorutil.New("panic on headless action: %v", r)
}
@@ -100,6 +106,8 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, var
if waitFunc != nil {
waitFuncs = append(waitFuncs, waitFunc)
}
case ActionWaitDialog:
err = p.HandleDialog(act, outData)
case ActionFilesInput:
if p.options.Options.AllowLocalFileAccess {
err = p.FilesInput(act, outData)
@@ -142,7 +150,7 @@ type rule struct {
}
// WaitVisible waits until an element appears.
func (p *Page) WaitVisible(act *Action, out map[string]string) error {
func (p *Page) WaitVisible(act *Action, out ActionData) error {
timeout, err := getTimeout(p, act)
if err != nil {
return errors.Wrap(err, "Wrong timeout given")
@@ -217,7 +225,7 @@ func geTimeParameter(p *Page, act *Action, parameterName string, defaultValue ti
}
// ActionAddHeader executes a AddHeader action.
func (p *Page) ActionAddHeader(act *Action, out map[string]string) error {
func (p *Page) ActionAddHeader(act *Action, out ActionData) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -228,7 +236,7 @@ func (p *Page) ActionAddHeader(act *Action, out map[string]string) error {
}
// ActionSetHeader executes a SetHeader action.
func (p *Page) ActionSetHeader(act *Action, out map[string]string) error {
func (p *Page) ActionSetHeader(act *Action, out ActionData) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -239,7 +247,7 @@ func (p *Page) ActionSetHeader(act *Action, out map[string]string) error {
}
// ActionDeleteHeader executes a DeleteHeader action.
func (p *Page) ActionDeleteHeader(act *Action, out map[string]string) error {
func (p *Page) ActionDeleteHeader(act *Action, out ActionData) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -249,7 +257,7 @@ func (p *Page) ActionDeleteHeader(act *Action, out map[string]string) error {
}
// ActionSetBody executes a SetBody action.
func (p *Page) ActionSetBody(act *Action, out map[string]string) error {
func (p *Page) ActionSetBody(act *Action, out ActionData) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -259,7 +267,7 @@ func (p *Page) ActionSetBody(act *Action, out map[string]string) error {
}
// ActionSetMethod executes an SetMethod action.
func (p *Page) ActionSetMethod(act *Action, out map[string]string) error {
func (p *Page) ActionSetMethod(act *Action, out ActionData) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -269,7 +277,7 @@ func (p *Page) ActionSetMethod(act *Action, out map[string]string) error {
}
// NavigateURL executes an ActionLoadURL actions loading a URL for the page.
func (p *Page) NavigateURL(action *Action, out map[string]string, allvars map[string]interface{}) error {
func (p *Page) NavigateURL(action *Action, out ActionData, 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)
@@ -325,7 +333,7 @@ func (p *Page) NavigateURL(action *Action, out map[string]string, allvars map[st
}
// RunScript runs a script on the loaded page
func (p *Page) RunScript(action *Action, out map[string]string) error {
func (p *Page) RunScript(action *Action, out ActionData) error {
code := p.getActionArgWithDefaultValues(action, "code")
if code == "" {
return errinvalidArguments
@@ -346,7 +354,7 @@ func (p *Page) RunScript(action *Action, out map[string]string) error {
}
// ClickElement executes click actions for an element.
func (p *Page) ClickElement(act *Action, out map[string]string) error {
func (p *Page) ClickElement(act *Action, out ActionData) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -361,12 +369,12 @@ func (p *Page) ClickElement(act *Action, out map[string]string) error {
}
// KeyboardAction executes a keyboard action on the page.
func (p *Page) KeyboardAction(act *Action, out map[string]string) error {
func (p *Page) KeyboardAction(act *Action, out ActionData) error {
return p.page.Keyboard.Type([]input.Key(p.getActionArgWithDefaultValues(act, "keys"))...)
}
// RightClickElement executes right click actions for an element.
func (p *Page) RightClickElement(act *Action, out map[string]string) error {
func (p *Page) RightClickElement(act *Action, out ActionData) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -381,7 +389,7 @@ func (p *Page) RightClickElement(act *Action, out map[string]string) error {
}
// Screenshot executes screenshot action on a page
func (p *Page) Screenshot(act *Action, out map[string]string) error {
func (p *Page) Screenshot(act *Action, out ActionData) error {
to := p.getActionArgWithDefaultValues(act, "to")
if to == "" {
to = ksuid.New().String()
@@ -444,7 +452,7 @@ func (p *Page) Screenshot(act *Action, out map[string]string) error {
}
// InputElement executes input element actions for an element.
func (p *Page) InputElement(act *Action, out map[string]string) error {
func (p *Page) InputElement(act *Action, out ActionData) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -463,7 +471,7 @@ func (p *Page) InputElement(act *Action, out map[string]string) error {
}
// TimeInputElement executes time input on an element
func (p *Page) TimeInputElement(act *Action, out map[string]string) error {
func (p *Page) TimeInputElement(act *Action, out ActionData) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -486,7 +494,7 @@ func (p *Page) TimeInputElement(act *Action, out map[string]string) error {
}
// SelectInputElement executes select input statement action on a element
func (p *Page) SelectInputElement(act *Action, out map[string]string) error {
func (p *Page) SelectInputElement(act *Action, out ActionData) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -511,7 +519,7 @@ func (p *Page) SelectInputElement(act *Action, out map[string]string) error {
}
// WaitLoad waits for the page to load
func (p *Page) WaitLoad(act *Action, out map[string]string) error {
func (p *Page) WaitLoad(act *Action, out ActionData) error {
p.page.Timeout(2 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
// Wait for the window.onload event and also wait for the network requests
@@ -525,7 +533,7 @@ func (p *Page) WaitLoad(act *Action, out map[string]string) error {
}
// GetResource gets a resource from an element from page.
func (p *Page) GetResource(act *Action, out map[string]string) error {
func (p *Page) GetResource(act *Action, out ActionData) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -541,7 +549,7 @@ func (p *Page) GetResource(act *Action, out map[string]string) error {
}
// FilesInput acts with a file input element on page
func (p *Page) FilesInput(act *Action, out map[string]string) error {
func (p *Page) FilesInput(act *Action, out ActionData) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -558,7 +566,7 @@ func (p *Page) FilesInput(act *Action, out map[string]string) error {
}
// ExtractElement extracts from an element on the page.
func (p *Page) ExtractElement(act *Action, out map[string]string) error {
func (p *Page) ExtractElement(act *Action, out ActionData) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -592,7 +600,7 @@ func (p *Page) ExtractElement(act *Action, out map[string]string) error {
}
// WaitEvent waits for an event to happen on the page.
func (p *Page) WaitEvent(act *Action, out map[string]string) (func() error, error) {
func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
event := p.getActionArgWithDefaultValues(act, "event")
if event == "" {
return nil, errors.New("event not recognized")
@@ -630,6 +638,43 @@ func (p *Page) WaitEvent(act *Action, out map[string]string) (func() error, erro
return waitFunc, nil
}
// HandleDialog handles JavaScript dialog (alert, confirm, prompt, or onbeforeunload).
func (p *Page) HandleDialog(act *Action, out ActionData) error {
maxDuration := 10 * time.Second
if dur := p.getActionArgWithDefaultValues(act, "max-duration"); dur != "" {
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)
defer cancel()
wait, handle := p.page.HandleDialog()
fn := func() (*proto.PageJavascriptDialogOpening, error) {
dialog := wait()
err := handle(&proto.PageHandleJavaScriptDialog{
Accept: true,
PromptText: "",
})
return dialog, err
}
dialog, err := contextutil.ExecFuncWithTwoReturns(ctx, fn)
if err == nil && act.Name != "" {
out[act.Name] = true
out[act.Name+"_type"] = string(dialog.Type)
out[act.Name+"_message"] = dialog.Message
}
return nil
}
// pageElementBy returns a page element from a variety of inputs.
//
// Supported values for by: r -> selector & regex, x -> xpath, js -> eval js,
@@ -664,14 +709,14 @@ func (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {
}
// DebugAction enables debug action on a page.
func (p *Page) DebugAction(act *Action, out map[string]string) error {
func (p *Page) DebugAction(act *Action, out ActionData) error {
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
func (p *Page) SleepAction(act *Action, out map[string]string) error {
func (p *Page) SleepAction(act *Action, out ActionData) error {
seconds := act.Data["duration"]
if seconds == "" {
seconds = "5"
@@ -38,7 +38,7 @@ func TestActionNavigate(t *testing.T) {
actions := []*Action{{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}}, {ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}}}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nilf(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
})
@@ -63,7 +63,7 @@ func TestActionScript(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},
}
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
require.Equal(t, "some-data", out["test"], "could not run js and get results correctly")
@@ -77,7 +77,7 @@ func TestActionScript(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: "test", Data: map[string]string{"code": "() => window.test"}},
}
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
require.Equal(t, "some-data", out["test"], "could not run js and get results correctly with js hook")
@@ -101,7 +101,7 @@ func TestActionClick(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
el := page.Page().MustElement("button")
@@ -134,7 +134,7 @@ func TestActionRightClick(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionRightClick}, Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
el := page.Page().MustElement("button")
@@ -159,7 +159,7 @@ func TestActionTextInput(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionTextInput}, Data: map[string]string{"selector": "input", "value": "test"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
el := page.Page().MustElement("input")
@@ -182,7 +182,7 @@ func TestActionHeadersChange(t *testing.T) {
}
}
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
})
@@ -205,7 +205,7 @@ func TestActionScreenshot(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
_ = page.Page()
@@ -233,7 +233,7 @@ func TestActionScreenshotToDir(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{"to": filePath, "mkdir": "true"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
_ = page.Page()
@@ -260,7 +260,7 @@ func TestActionTimeInput(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionTimeInput}, Data: map[string]string{"selector": "input", "value": "2006-01-02T15:04:05Z"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
el := page.Page().MustElement("input")
@@ -288,7 +288,7 @@ func TestActionSelectInput(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionSelectInput}, Data: map[string]string{"by": "x", "xpath": "//select[@id='test']", "value": "Test2", "selected": "true"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
el := page.Page().MustElement("select")
require.Equal(t, "Test2", el.MustText(), "could not get input change value")
@@ -311,7 +311,7 @@ func TestActionFilesInput(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{"selector": "input", "value": "test1.pdf"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
el := page.Page().MustElement("input")
@@ -337,7 +337,7 @@ func TestActionFilesInputNegative(t *testing.T) {
}
t.Setenv("LOCAL_FILE_ACCESS", "false")
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.ErrorContains(t, err, ErrLFAccessDenied.Error(), "got file access when -lfa is false")
})
}
@@ -359,7 +359,7 @@ func TestActionWaitLoad(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
el := page.Page().MustElement("button")
style, attributeErr := el.Attribute("style")
@@ -384,9 +384,12 @@ func TestActionGetResource(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionGetResource}, Data: map[string]string{"by": "x", "xpath": "//img[@id='test']"}, Name: "src"},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, len(out["src"]), 121808, "could not find resource")
src, ok := out["src"].(string)
require.True(t, ok, "could not assert src to string")
require.Equal(t, len(src), 121808, "could not find resource")
})
}
@@ -404,7 +407,7 @@ func TestActionExtract(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionExtract}, Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}, Name: "extract"},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "Wait for me!", out["extract"], "could not extract text")
})
@@ -423,7 +426,7 @@ func TestActionSetMethod(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionSetMethod}, Data: map[string]string{"part": "x", "method": "SET"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "SET", page.rules[0].Args["method"], "could not find resource")
})
@@ -442,7 +445,7 @@ func TestActionAddHeader(t *testing.T) {
}
}
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
})
@@ -463,7 +466,7 @@ func TestActionDeleteHeader(t *testing.T) {
}
}
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "header deleted", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not delete header correctly")
})
@@ -481,7 +484,7 @@ func TestActionSetBody(t *testing.T) {
_, _ = fmt.Fprintln(w, string(body))
}
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.Equal(t, "hello", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
})
@@ -505,7 +508,7 @@ func TestActionKeyboard(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionKeyboard}, Data: map[string]string{"keys": "Test2"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
el := page.Page().MustElement("input")
require.Equal(t, "Test2", el.MustText(), "could not get input change value")
@@ -529,7 +532,7 @@ func TestActionSleep(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionSleep}, Data: map[string]string{"duration": "2"}},
}
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
require.True(t, page.Page().MustElement("button").MustVisible(), "could not get button")
})
@@ -553,7 +556,7 @@ func TestActionWaitVisible(t *testing.T) {
}
t.Run("wait for an element being visible", func(t *testing.T) {
testHeadlessSimpleResponse(t, response, actions, 2*time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, 2*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
page.Page().MustElement("button").MustVisible()
@@ -562,21 +565,82 @@ func TestActionWaitVisible(t *testing.T) {
t.Run("timeout because of element not visible", func(t *testing.T) {
// increased timeout from time.Second/2 to time.Second due to random fails (probably due to overhead and system)
testHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out map[string]string) {
testHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out ActionData) {
require.Error(t, err)
require.Contains(t, err.Error(), "Element did not appear in the given amount of time")
})
})
}
func testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action, timeout time.Duration, assert func(page *Page, pageErr error, out map[string]string)) {
func TestActionWaitDialog(t *testing.T) {
response := `<html>
<head>
<title>Nuclei Test Page</title>
</head>
<body>
<script type="text/javascript">
const urlParams = new URLSearchParams(window.location.search);
const scriptContent = urlParams.get('script');
if (scriptContent) {
const scriptElement = document.createElement('script');
scriptElement.textContent = scriptContent;
document.body.appendChild(scriptElement);
}
</script>
</body>
</html>`
t.Run("Triggered", func(t *testing.T) {
actions := []*Action{
{
ActionType: ActionTypeHolder{ActionType: ActionNavigate},
Data: map[string]string{"url": "{{BaseURL}}/?script=alert%281%29"},
},
{
ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},
Name: "test",
},
}
testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
test, ok := out["test"].(bool)
require.True(t, ok, "could not assert test to bool")
require.True(t, test, "could not find test")
})
})
t.Run("Invalid", func(t *testing.T) {
actions := []*Action{
{
ActionType: ActionTypeHolder{ActionType: ActionNavigate},
Data: map[string]string{"url": "{{BaseURL}}/?script=foo"},
},
{
ActionType: ActionTypeHolder{ActionType: ActionWaitDialog},
Name: "test",
},
}
testHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {
require.Nil(t, err, "could not run page actions")
_, ok := out["test"].(bool)
require.False(t, ok, "output assertion is success")
})
})
}
func testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action, timeout time.Duration, assert func(page *Page, pageErr error, out ActionData)) {
t.Helper()
testHeadless(t, actions, timeout, func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, response)
}, assert)
}
func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData map[string]string)) {
func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData ActionData)) {
t.Helper()
lfa := getBoolFromEnv("LOCAL_FILE_ACCESS", true)
+7 -1
View File
@@ -183,7 +183,13 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p
responseBody, _ = html.HTML()
}
outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, navigatedURL, page.DumpHistory())
header := out.GetOrDefault("header", "").(string)
// NOTE(dwisiswant0): `status_code` key should be an integer type.
// Ref: https://github.com/projectdiscovery/nuclei/pull/5545#discussion_r1721291013
statusCode := out.GetOrDefault("status_code", "").(string)
outputEvent := request.responseToDSLMap(responseBody, header, statusCode, reqBuilder.String(), input.MetaInput.Input, navigatedURL, page.DumpHistory())
// add response fields to template context and merge templatectx variables to output event
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)
if request.options.HasTemplateCtx(input.MetaInput) {
+2 -2
View File
@@ -155,14 +155,14 @@ func (request *Request) executeOnTarget(input *contextargs.Context, visited maps
}
visited.Set(actualAddress, struct{}{})
if err := request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, callback); err != nil {
if err = request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, callback); err != nil {
outputEvent := request.responseToDSLMap("", "", "", address, "")
callback(&output.InternalWrappedEvent{InternalEvent: outputEvent})
gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err)
continue
}
}
return nil
return err
}
// executeAddress executes the request for an address
+1 -1
View File
@@ -86,7 +86,7 @@ func TestNetworkExecuteWithResults(t *testing.T) {
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
require.Nil(t, err, "could not execute network request")
require.NotNil(t, err, "could not execute network request")
})
require.Nil(t, finalEvent.Results, "could not get event output from request")
+3
View File
@@ -12,6 +12,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear"
"github.com/projectdiscovery/retryablehttp-go"
)
@@ -29,6 +30,8 @@ type Options struct {
Gitea *gitea.Options `yaml:"gitea"`
// Jira contains configuration options for Jira Issue Tracker
Jira *jira.Options `yaml:"jira"`
// Linear contains configuration options for Linear Issue Tracker
Linear *linear.Options `yaml:"linear"`
// MarkdownExporter contains configuration options for Markdown Exporter Module
MarkdownExporter *markdown.Options `yaml:"markdown"`
// SarifExporter contains configuration options for Sarif Exporter Module
+11
View File
@@ -28,6 +28,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear"
errorutil "github.com/projectdiscovery/utils/errors"
fileutil "github.com/projectdiscovery/utils/file"
)
@@ -112,6 +113,15 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) {
}
client.trackers = append(client.trackers, tracker)
}
if options.Linear != nil {
options.Linear.HttpClient = options.HttpClient
options.Linear.OmitRaw = options.OmitRaw
tracker, err := linear.New(options.Linear)
if err != nil {
return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation)
}
client.trackers = append(client.trackers, tracker)
}
if options.MarkdownExporter != nil {
exporter, err := markdown.New(options.MarkdownExporter)
if err != nil {
@@ -195,6 +205,7 @@ func CreateConfigIfNotExists() error {
GitLab: &gitlab.Options{},
Gitea: &gitea.Options{},
Jira: &jira.Options{},
Linear: &linear.Options{},
MarkdownExporter: &markdown.Options{},
SarifExporter: &sarif.Options{},
ElasticsearchExporter: &es.Options{},
@@ -0,0 +1,312 @@
// Package jsonutil provides a function for decoding JSON
// into a GraphQL query data structure.
//
// Taken from: https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/internal/jsonutil/graphql.go
package jsonutil
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strings"
)
// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores
// the result in the GraphQL query data structure pointed to by v.
//
// The implementation is created on top of the JSON tokenizer available
// in "encoding/json".Decoder.
func UnmarshalGraphQL(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
err := (&decoder{tokenizer: dec}).Decode(v)
if err != nil {
return err
}
tok, err := dec.Token()
switch err {
case io.EOF:
// Expect to get io.EOF. There shouldn't be any more
// tokens left after we've decoded v successfully.
return nil
case nil:
return fmt.Errorf("invalid token '%v' after top-level value", tok)
default:
return err
}
}
// decoder is a JSON decoder that performs custom unmarshaling behavior
// for GraphQL query data structures. It's implemented on top of a JSON tokenizer.
type decoder struct {
tokenizer interface {
Token() (json.Token, error)
}
// Stack of what part of input JSON we're in the middle of - objects, arrays.
parseState []json.Delim
// Stacks of values where to unmarshal.
// The top of each stack is the reflect.Value where to unmarshal next JSON value.
//
// The reason there's more than one stack is because we might be unmarshaling
// a single JSON value into multiple GraphQL fragments or embedded structs, so
// we keep track of them all.
vs [][]reflect.Value
}
// Decode decodes a single JSON value from d.tokenizer into v.
func (d *decoder) Decode(v any) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("cannot decode into non-pointer %T", v)
}
d.vs = [][]reflect.Value{{rv.Elem()}}
return d.decode()
}
// decode decodes a single JSON value from d.tokenizer into d.vs.
func (d *decoder) decode() error {
// The loop invariant is that the top of each d.vs stack
// is where we try to unmarshal the next JSON value we see.
for len(d.vs) > 0 {
tok, err := d.tokenizer.Token()
if err == io.EOF {
return errors.New("unexpected end of JSON input")
} else if err != nil {
return err
}
switch {
// Are we inside an object and seeing next key (rather than end of object)?
case d.state() == '{' && tok != json.Delim('}'):
key, ok := tok.(string)
if !ok {
return errors.New("unexpected non-key in JSON input")
}
someFieldExist := false
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var f reflect.Value
if v.Kind() == reflect.Struct {
f = fieldByGraphQLName(v, key)
if f.IsValid() {
someFieldExist = true
}
}
d.vs[i] = append(d.vs[i], f)
}
if !someFieldExist {
return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
}
// We've just consumed the current token, which was the key.
// Read the next token, which should be the value, and let the rest of code process it.
tok, err = d.tokenizer.Token()
if err == io.EOF {
return errors.New("unexpected end of JSON input")
} else if err != nil {
return err
}
// Are we inside an array and seeing next value (rather than end of array)?
case d.state() == '[' && tok != json.Delim(']'):
someSliceExist := false
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var f reflect.Value
if v.Kind() == reflect.Slice {
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).
f = v.Index(v.Len() - 1)
someSliceExist = true
}
d.vs[i] = append(d.vs[i], f)
}
if !someSliceExist {
return fmt.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))
}
}
switch tok := tok.(type) {
case string, json.Number, bool, nil:
// Value.
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
if !v.IsValid() {
continue
}
err := unmarshalValue(tok, v)
if err != nil {
return err
}
}
d.popAllVs()
case json.Delim:
switch tok {
case '{':
// Start of object.
d.pushState(tok)
frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
frontier[i] = v
// TODO: Do this recursively or not? Add a test case if needed.
if v.Kind() == reflect.Ptr && v.IsNil() {
v.Set(reflect.New(v.Type().Elem())) // v = new(T).
}
}
// Find GraphQL fragments/embedded structs recursively, adding to frontier
// as new ones are discovered and exploring them further.
for len(frontier) > 0 {
v := frontier[0]
frontier = frontier[1:]
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
continue
}
for i := 0; i < v.NumField(); i++ {
if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {
// Add GraphQL fragment or embedded struct.
d.vs = append(d.vs, []reflect.Value{v.Field(i)})
frontier = append(frontier, v.Field(i))
}
}
}
case '[':
// Start of array.
d.pushState(tok)
for i := range d.vs {
v := d.vs[i][len(d.vs[i])-1]
// TODO: Confirm this is needed, write a test case.
//if v.Kind() == reflect.Ptr && v.IsNil() {
// v.Set(reflect.New(v.Type().Elem())) // v = new(T).
//}
// Reset slice to empty (in case it had non-zero initial value).
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Slice {
continue
}
v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).
}
case '}', ']':
// End of object or array.
d.popAllVs()
d.popState()
default:
return errors.New("unexpected delimiter in JSON input")
}
default:
return errors.New("unexpected token in JSON input")
}
}
return nil
}
// pushState pushes a new parse state s onto the stack.
func (d *decoder) pushState(s json.Delim) {
d.parseState = append(d.parseState, s)
}
// popState pops a parse state (already obtained) off the stack.
// The stack must be non-empty.
func (d *decoder) popState() {
d.parseState = d.parseState[:len(d.parseState)-1]
}
// state reports the parse state on top of stack, or 0 if empty.
func (d *decoder) state() json.Delim {
if len(d.parseState) == 0 {
return 0
}
return d.parseState[len(d.parseState)-1]
}
// popAllVs pops from all d.vs stacks, keeping only non-empty ones.
func (d *decoder) popAllVs() {
var nonEmpty [][]reflect.Value
for i := range d.vs {
d.vs[i] = d.vs[i][:len(d.vs[i])-1]
if len(d.vs[i]) > 0 {
nonEmpty = append(nonEmpty, d.vs[i])
}
}
d.vs = nonEmpty
}
// fieldByGraphQLName returns an exported struct field of struct v
// that matches GraphQL name, or invalid reflect.Value if none found.
func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {
for i := 0; i < v.NumField(); i++ {
if v.Type().Field(i).PkgPath != "" {
// Skip unexported field.
continue
}
if hasGraphQLName(v.Type().Field(i), name) {
return v.Field(i)
}
}
return reflect.Value{}
}
// hasGraphQLName reports whether struct field f has GraphQL name.
func hasGraphQLName(f reflect.StructField, name string) bool {
value, ok := f.Tag.Lookup("graphql")
if !ok {
// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.
//return caseconv.MixedCapsToLowerCamelCase(f.Name) == name
return strings.EqualFold(f.Name, name)
}
value = strings.TrimSpace(value) // TODO: Parse better.
if strings.HasPrefix(value, "...") {
// GraphQL fragment. It doesn't have a name.
return false
}
// Cut off anything that follows the field name,
// such as field arguments, aliases, directives.
if i := strings.IndexAny(value, "(:@"); i != -1 {
value = value[:i]
}
return strings.TrimSpace(value) == name
}
// isGraphQLFragment reports whether struct field f is a GraphQL fragment.
func isGraphQLFragment(f reflect.StructField) bool {
value, ok := f.Tag.Lookup("graphql")
if !ok {
return false
}
value = strings.TrimSpace(value) // TODO: Parse better.
return strings.HasPrefix(value, "...")
}
// unmarshalValue unmarshals JSON value into v.
// v must be addressable and not obtained by the use of unexported
// struct fields, otherwise unmarshalValue will panic.
func unmarshalValue(value json.Token, v reflect.Value) error {
b, err := json.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).
if err != nil {
return err
}
return json.Unmarshal(b, v.Addr().Interface())
}
+409
View File
@@ -0,0 +1,409 @@
package linear
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/shurcooL/graphql"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear/jsonutil"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/retryablehttp-go"
)
// Integration is a client for linear issue tracker integration
type Integration struct {
url string
httpclient *http.Client
options *Options
}
// Options contains the configuration options for linear issue tracker client
type Options struct {
// APIKey is the API key for linear account.
APIKey string `yaml:"api-key" validate:"required"`
// AllowList contains a list of allowed events for this tracker
AllowList *filters.Filter `yaml:"allow-list"`
// DenyList contains a list of denied events for this tracker
DenyList *filters.Filter `yaml:"deny-list"`
// TeamID is the team id for the project
TeamID string `yaml:"team-id"`
// ProjectID is the project id for the project
ProjectID string `yaml:"project-id"`
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
// OpenStateID is the id of the open state for the project
OpenStateID string `yaml:"open-state-id"`
HttpClient *retryablehttp.Client `yaml:"-"`
OmitRaw bool `yaml:"-"`
}
// New creates a new issue tracker integration client based on options.
func New(options *Options) (*Integration, error) {
httpClient := &http.Client{
Transport: &addHeaderTransport{
T: http.DefaultTransport,
Key: options.APIKey,
},
}
integration := &Integration{
url: "https://api.linear.app/graphql",
options: options,
httpclient: httpClient,
}
return integration, nil
}
// CreateIssue creates an issue in the tracker
func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
summary := format.Summary(event)
description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
_ = description
ctx := context.Background()
var err error
var existingIssue *linearIssue
if i.options.DuplicateIssueCheck {
existingIssue, err = i.findIssueByTitle(ctx, summary)
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
}
if existingIssue == nil {
// Create a new issue
createdIssue, err := i.createIssueLinear(ctx, summary, description, priorityFromSeverity(event.Info.SeverityHolder.Severity))
if err != nil {
return nil, err
}
return &filters.CreateIssueResponse{
IssueID: types.ToString(createdIssue.ID),
IssueURL: types.ToString(createdIssue.URL),
}, nil
} else {
if existingIssue.State.Name == "Done" {
// Update the issue state to open
var issueUpdateInput struct {
StateID string `json:"stateId"`
}
issueUpdateInput.StateID = i.options.OpenStateID
variables := map[string]interface{}{
"issueUpdateInput": issueUpdateInput,
"issueID": types.ToString(existingIssue.ID),
}
var resp struct {
IssueUpdate struct {
LastSyncID int `json:"lastSyncId"`
}
}
err := i.doGraphqlRequest(ctx, existingIssueUpdateStateMutation, &resp, variables, "IssueUpdate")
if err != nil {
return nil, fmt.Errorf("error reopening issue %s: %s", existingIssue.ID, err)
}
}
commentInput := map[string]interface{}{
"issueId": types.ToString(existingIssue.ID),
"body": description,
}
variables := map[string]interface{}{
"commentCreateInput": commentInput,
}
var resp struct {
CommentCreate struct {
LastSyncID int `json:"lastSyncId"`
}
}
err := i.doGraphqlRequest(ctx, commentCreateExistingTicketMutation, &resp, variables, "CommentCreate")
if err != nil {
return nil, fmt.Errorf("error commenting on issue %s: %s", existingIssue.ID, err)
}
return &filters.CreateIssueResponse{
IssueID: types.ToString(existingIssue.ID),
IssueURL: types.ToString(existingIssue.URL),
}, nil
}
}
func priorityFromSeverity(sev severity.Severity) float64 {
switch sev {
case severity.Critical:
return linearPriorityCritical
case severity.High:
return linearPriorityHigh
case severity.Medium:
return linearPriorityMedium
case severity.Low:
return linearPriorityLow
default:
return linearPriorityNone
}
}
type createIssueMutation struct {
IssueCreate struct {
Issue struct {
ID graphql.ID
Title graphql.String
Identifier graphql.String
State struct {
Name graphql.String
}
URL graphql.String
}
}
}
const (
createIssueGraphQLMutation = `mutation CreateIssue($input: IssueCreateInput!) {
issueCreate(input: $input) {
issue {
id
title
identifier
state {
name
}
url
}
}
}`
searchExistingTicketQuery = `query ($teamID: ID, $projectID: ID, $title: String!) {
issues(filter: {
title: { eq: $title },
team: { id: { eq: $teamID } }
project: { id: { eq: $projectID } }
}) {
nodes {
id
title
identifier
state {
name
}
url
}
}
}
`
existingIssueUpdateStateMutation = `mutation IssueUpdate($issueUpdateInput: IssueUpdateInput!, $issueID: String!) {
issueUpdate(input: $issueUpdateInput, id: $issueID) {
lastSyncId
}
}
`
commentCreateExistingTicketMutation = `mutation CommentCreate($commentCreateInput: CommentCreateInput!) {
commentCreate(input: $commentCreateInput) {
lastSyncId
}
}
`
)
func (i *Integration) createIssueLinear(ctx context.Context, title, description string, priority float64) (*linearIssue, error) {
var mutation createIssueMutation
input := map[string]interface{}{
"title": title,
"description": description,
"priority": priority,
}
if i.options.TeamID != "" {
input["teamId"] = graphql.ID(i.options.TeamID)
}
if i.options.ProjectID != "" {
input["projectId"] = i.options.ProjectID
}
variables := map[string]interface{}{
"input": input,
}
err := i.doGraphqlRequest(ctx, createIssueGraphQLMutation, &mutation, variables, "CreateIssue")
if err != nil {
return nil, err
}
return &linearIssue{
ID: mutation.IssueCreate.Issue.ID,
Title: mutation.IssueCreate.Issue.Title,
Identifier: mutation.IssueCreate.Issue.Identifier,
State: struct {
Name graphql.String
}{
Name: mutation.IssueCreate.Issue.State.Name,
},
URL: mutation.IssueCreate.Issue.URL,
}, nil
}
func (i *Integration) findIssueByTitle(ctx context.Context, title string) (*linearIssue, error) {
var query findExistingIssuesSearch
variables := map[string]interface{}{
"title": graphql.String(title),
}
if i.options.TeamID != "" {
variables["teamId"] = graphql.ID(i.options.TeamID)
}
if i.options.ProjectID != "" {
variables["projectID"] = graphql.ID(i.options.ProjectID)
}
err := i.doGraphqlRequest(ctx, searchExistingTicketQuery, &query, variables, "")
if err != nil {
return nil, err
}
if len(query.Issues.Nodes) > 0 {
return &query.Issues.Nodes[0], nil
}
return nil, io.EOF
}
func (i *Integration) Name() string {
return "linear"
}
func (i *Integration) CloseIssue(event *output.ResultEvent) error {
// TODO: Unimplemented for now as not used in many places
// and overhead of maintaining our own API for this.
// This is too much code as it is :(
return nil
}
// ShouldFilter determines if an issue should be logged to this tracker
func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
if i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {
return false
}
if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
return false
}
return true
}
type linearIssue struct {
ID graphql.ID
Title graphql.String
Identifier graphql.String
State struct {
Name graphql.String
}
URL graphql.String
}
type findExistingIssuesSearch struct {
Issues struct {
Nodes []linearIssue
}
}
// Custom transport to add the API key to the header
type addHeaderTransport struct {
T http.RoundTripper
Key string
}
func (adt *addHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("Authorization", adt.Key)
return adt.T.RoundTrip(req)
}
const (
linearPriorityNone = float64(0)
linearPriorityCritical = float64(1)
linearPriorityHigh = float64(2)
linearPriorityMedium = float64(3)
linearPriorityLow = float64(4)
)
// errors represents the "errors" array in a response from a GraphQL server.
// If returned via error interface, the slice is expected to contain at least 1 element.
//
// Specification: https://spec.graphql.org/October2021/#sec-Errors.
type errorsGraphql []struct {
Message string
Locations []struct {
Line int
Column int
}
}
// Error implements error interface.
func (e errorsGraphql) Error() string {
return e[0].Message
}
// do executes a single GraphQL operation.
func (i *Integration) doGraphqlRequest(ctx context.Context, query string, v any, variables map[string]any, operationName string) error {
in := struct {
Query string `json:"query"`
Variables map[string]any `json:"variables,omitempty"`
OperationName string `json:"operationName,omitempty"`
}{
Query: query,
Variables: variables,
OperationName: operationName,
}
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(in)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, i.url, &buf)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := i.httpclient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("non-200 OK status code: %v body: %q", resp.Status, body)
}
var out struct {
Data *json.RawMessage
Errors errorsGraphql
//Extensions any // Unused.
}
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return err
}
if out.Data != nil {
err := jsonutil.UnmarshalGraphQL(*out.Data, v)
if err != nil {
return err
}
}
if len(out.Errors) > 0 {
return out.Errors
}
return nil
}
+5 -1
View File
@@ -52,6 +52,10 @@ func (s *ScanContext) Context() context.Context {
return s.ctx
}
func (s *ScanContext) GenerateErrorMessage() string {
return joinErrors(s.errors)
}
// GenerateResult returns final results slice from all events
func (s *ScanContext) GenerateResult() []*output.ResultEvent {
s.m.Lock()
@@ -96,7 +100,7 @@ func (s *ScanContext) LogError(err error) {
}
s.errors = append(s.errors, err)
errorMessage := joinErrors(s.errors)
errorMessage := s.GenerateErrorMessage()
for _, result := range s.results {
result.Error = errorMessage
+1
View File
@@ -1612,6 +1612,7 @@ func init() {
"deleteheader",
"setbody",
"waitevent",
"dialog",
"keyboard",
"debug",
"sleep",
+57
View File
@@ -10,6 +10,7 @@ import (
"github.com/dop251/goja"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
@@ -19,6 +20,8 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/multiproto"
"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr"
"github.com/projectdiscovery/utils/errkit"
)
// TemplateExecutor is an executor for a template
@@ -126,6 +129,8 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
executed := &atomic.Bool{}
// matched in this case means something was exported / written to output
matched := &atomic.Bool{}
// callbackCalled tracks if the callback was called or not
callbackCalled := &atomic.Bool{}
defer func() {
// it is essential to remove template context of `Scan i.e template x input pair`
// since it is of no use after scan is completed (regardless of success or failure)
@@ -143,6 +148,7 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
}
ctx.OnResult = func(event *output.InternalWrappedEvent) {
callbackCalled.Store(true)
if event == nil {
// something went wrong
return
@@ -198,13 +204,64 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
} else {
errx = e.engine.ExecuteWithResults(ctx)
}
ctx.LogError(errx)
if lastMatcherEvent != nil {
lastMatcherEvent.InternalEvent["error"] = tryParseCause(fmt.Errorf("%s", ctx.GenerateErrorMessage()))
writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus)
}
//TODO: this is a hacky way to handle the case where the callback is not called and matcher-status is true.
// This is a workaround and needs to be refactored.
// Check if callback was never called and matcher-status is true
if !callbackCalled.Load() && e.options.Options.MatcherStatus {
fakeEvent := &output.InternalWrappedEvent{
Results: []*output.ResultEvent{
{
TemplateID: e.options.TemplateID,
Info: e.options.TemplateInfo,
Type: e.getTemplateType(),
Host: ctx.Input.MetaInput.Input,
Error: tryParseCause(fmt.Errorf("%s", ctx.GenerateErrorMessage())),
},
},
OperatorsResult: &operators.Result{
Matched: false,
},
}
writeFailureCallback(fakeEvent, e.options.Options.MatcherStatus)
}
return executed.Load() || matched.Load(), errx
}
// tryParseCause tries to parse the cause of given error
// this is legacy support due to use of errorutil in existing libraries
// but this should not be required once all libraries are updated
func tryParseCause(err error) string {
errStr := ""
errX := errkit.FromError(err)
if errX != nil {
var errCause error
if len(errX.Errors()) > 1 {
errCause = errX.Errors()[0]
}
if errCause == nil {
errCause = errX
}
msg := strings.Trim(errCause.Error(), "{} ")
parts := strings.Split(msg, ":")
errCause = errkit.New("%s", parts[len(parts)-1])
errKind := errkit.GetErrorKind(err, nucleierr.ErrTemplateLogic).String()
errStr = errCause.Error()
errStr = strings.TrimSpace(strings.Replace(errStr, "errKind="+errKind, "", -1))
}
return errStr
}
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (e *TemplateExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
var errx error
+7
View File
@@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
errorutil "github.com/projectdiscovery/utils/errors"
fileutil "github.com/projectdiscovery/utils/file"
@@ -201,7 +202,13 @@ func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error {
}
}()
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
f.ctx.LogError(fmt.Errorf("panic occurred while executing flow: %v", r))
}