mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-07 17:43:04 -10:00
Compare commits
118
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ec818ba3d | ||
|
|
a9286653fe | ||
|
|
aeaad62d28 | ||
|
|
d68af67e6e | ||
|
|
888a732fbc | ||
|
|
3f0de96726 | ||
|
|
1cd42c46c7 | ||
|
|
82680980a5 | ||
|
|
690089e1ce | ||
|
|
f0624820d3 | ||
|
|
7ba5d51b00 | ||
|
|
8b9acb2927 | ||
|
|
23825c77b2 | ||
|
|
d1614857bd | ||
|
|
1f945d6d50 | ||
|
|
e4dae52d5a | ||
|
|
c9f67897c4 | ||
|
|
c93a2b1ba4 | ||
|
|
6a561c6470 | ||
|
|
828dac9002 | ||
|
|
9983d7415c | ||
|
|
a118daa375 | ||
|
|
b8e818ac29 | ||
|
|
3e6c62b03b | ||
|
|
182e000f2c | ||
|
|
21205253c3 | ||
|
|
b18f99e462 | ||
|
|
694835c459 | ||
|
|
3eee9678d0 | ||
|
|
b69de15777 | ||
|
|
1610d96bc2 | ||
|
|
6347efa590 | ||
|
|
4cd065df5f | ||
|
|
3d2f31a56f | ||
|
|
0a536e6ce0 | ||
|
|
c8cc419e81 | ||
|
|
87e99be4f6 | ||
|
|
2ac9aaf871 | ||
|
|
a45e4bbd19 | ||
|
|
72da91a399 | ||
|
|
bd6330f72a | ||
|
|
8b1d9aa0d7 | ||
|
|
39f8be2125 | ||
|
|
59e69b561d | ||
|
|
fcdead9157 | ||
|
|
f7f8d53019 | ||
|
|
4ef78ae5fa | ||
|
|
e88c7e31d0 | ||
|
|
7f4cdf6196 | ||
|
|
6b0ba0f4f3 | ||
|
|
cc332979aa | ||
|
|
2b5a8f5b63 | ||
|
|
f30d0b3b8d | ||
|
|
042b33de3d | ||
|
|
5bb730849c | ||
|
|
7bc257d7a5 | ||
|
|
04a6159713 | ||
|
|
0c4c0af4e9 | ||
|
|
cb05f55251 | ||
|
|
841d8913e5 | ||
|
|
9a5272985c | ||
|
|
fdb8e0a8c6 | ||
|
|
88b281ae47 | ||
|
|
e0b2542868 | ||
|
|
6b71af448a | ||
|
|
bf58b4d756 | ||
|
|
aac1af1308 | ||
|
|
4a85e73fbb | ||
|
|
c23e1864d6 | ||
|
|
2670b6865f | ||
|
|
b424ba9f50 | ||
|
|
faed7798fc | ||
|
|
253fbc3619 | ||
|
|
4a39fdb8d6 | ||
|
|
5627da8c19 | ||
|
|
710f1ad240 | ||
|
|
56af26a502 | ||
|
|
8e1072e2b0 | ||
|
|
b53b530b6a | ||
|
|
bfdc507c6c | ||
|
|
ddf9b2fbec | ||
|
|
5e102b782b | ||
|
|
d1f4c98cd7 | ||
|
|
87c8cd8e01 | ||
|
|
35a0d673ad | ||
|
|
46782ff90c | ||
|
|
b1152efd42 | ||
|
|
d84aea93cc | ||
|
|
50023428d0 | ||
|
|
7af08e2b04 | ||
|
|
3064788d35 | ||
|
|
0da993afe6 | ||
|
|
4d5eb9c484 | ||
|
|
8a3818230d | ||
|
|
943a6f6643 | ||
|
|
091ca32b1c | ||
|
|
326f8774fd | ||
|
|
c94d226a49 | ||
|
|
c9fe07557c | ||
|
|
e16350cde6 | ||
|
|
0f79044dae | ||
|
|
c6bafd88eb | ||
|
|
80b3cc0627 | ||
|
|
02fadbcf77 | ||
|
|
f2f250738c | ||
|
|
1c76398aea | ||
|
|
0675aa48a3 | ||
|
|
f29b94521e | ||
|
|
d20ec34f63 | ||
|
|
1af29f97a9 | ||
|
|
1293a34707 | ||
|
|
c6e5bdd857 | ||
|
|
2f7eea410d | ||
|
|
2609d2d135 | ||
|
|
e0466e102c | ||
|
|
1e49cd9800 | ||
|
|
a5c7fe6094 | ||
|
|
f363b307d4 |
No files matched your search
@@ -0,0 +1,72 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us to improve the Nuclei.
|
||||
title: "[BUG] ..."
|
||||
labels: ["Type: Bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
For support requests, FAQs or "How to" questions, please use the [GitHub Discussions](https://github.com/projectdiscovery/nuclei/discussions) section instead or join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel.
|
||||
|
||||
:warning: **Issues missing important information may be closed without further investigation.**
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: |
|
||||
Steps to reproduce the behavior, for example, commands to run Nuclei.
|
||||
|
||||
:warning: **Please redact any literal target hosts/URLs or other sensitive information.**
|
||||
placeholder: |
|
||||
1. Run `nuclei -t ...`
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
Examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Nuclei** (`nuclei -version`): v3.3.1
|
||||
- **Go** (`go version`): go1.22.0 _(only if you've installed it via the `go install` command)_
|
||||
value: |
|
||||
- OS:
|
||||
- Nuclei:
|
||||
- Go:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Templates? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Feature Request
|
||||
description: Request feature to implement in the Nuclei.
|
||||
title: "[FEATURE] ..."
|
||||
labels: ["Type: Enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request!
|
||||
|
||||
Please make sure to provide a detailed description with all the relevant information that might be required to start working on this feature. In case you are not sure about your request or whether the particular feature is already supported or not, please [start a discussion](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas) instead.
|
||||
|
||||
Join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe your feature request
|
||||
description: A clear and concise description of feature to implement.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the use case of the feature
|
||||
description: A clear and concise description of the feature request's motivation and the use-cases in which it could be useful.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Request feature to implement in this project
|
||||
title: ""
|
||||
labels: 'Type: Enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
1. Please make sure to provide a detailed description with all the relevant information that might be required to start working on this feature.
|
||||
2. In case you are not sure about your request or whether the particular feature is already supported or not, please start a discussion instead.
|
||||
3. GitHub Discussion: https://github.com/projectdiscovery/nuclei/discussions/categories/ideas
|
||||
4. Join our discord server at https://discord.gg/projectdiscovery to discuss the idea on the #nuclei channel.
|
||||
-->
|
||||
|
||||
### Please describe your feature request:
|
||||
<!-- A clear and concise description of feature to implement -->
|
||||
|
||||
### Describe the use case of this feature:
|
||||
<!-- A clear and concise description of the feature request's motivation and the use-cases in which it could be useful. -->
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Issue report
|
||||
about: Create a report to help us to improve the project
|
||||
labels: 'Type: Bug'
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
1. Please search to see if an issue already exists for the bug you encountered.
|
||||
2. For support requests, FAQs or "How to" questions, please use the GitHub Discussions section instead - https://github.com/projectdiscovery/nuclei/discussions or
|
||||
3. Join our discord server at https://discord.gg/projectdiscovery and post the question on the #nuclei channel.
|
||||
-->
|
||||
|
||||
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
|
||||
|
||||
### Nuclei version:
|
||||
<!-- You can find current version of nuclei with "nuclei -version" -->
|
||||
<!-- We only accept issues that are reproducible on the latest version of nuclei. -->
|
||||
<!-- You can find the latest version of project at https://github.com/projectdiscovery/nuclei/releases/ -->
|
||||
|
||||
## Nuclei command:
|
||||
|
||||
<!-- Provide the exact command you used to run Nuclei. -->
|
||||
<!-- Please redact any literal target hosts/URLs or other sensitive information. -->
|
||||
|
||||
### Current Behavior:
|
||||
<!-- A concise description of what you're experiencing. -->
|
||||
|
||||
### Expected Behavior:
|
||||
<!-- A concise description of what you expected to happen. -->
|
||||
|
||||
### Steps To Reproduce:
|
||||
<!--
|
||||
Example: steps to reproduce the behavior:
|
||||
1. Run 'nuclei -t ... -u ..'
|
||||
2. See error...
|
||||
-->
|
||||
|
||||
|
||||
### Anything else:
|
||||
<!-- Links? References? Screenshots? Anything that will give us more context about the issue that you are encountering! -->
|
||||
@@ -0,0 +1,8 @@
|
||||
addReviewers: true
|
||||
reviewers:
|
||||
- dogancanbakir
|
||||
- dwisiswant0
|
||||
|
||||
numberOfReviewers: 1
|
||||
skipKeywords:
|
||||
- '@dependabot'
|
||||
@@ -27,10 +27,8 @@ jobs:
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Go Mod hygiene
|
||||
run: |
|
||||
go clean -modcache
|
||||
go mod tidy
|
||||
- name: Verify Go modules
|
||||
run: make verify
|
||||
|
||||
- name: Build
|
||||
run: go build .
|
||||
|
||||
@@ -22,10 +22,8 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: projectdiscovery/actions/setup/go@v1
|
||||
|
||||
- name: Go Mod hygine
|
||||
run: |
|
||||
go clean -modcache
|
||||
go mod tidy
|
||||
- name: Verify Go modules
|
||||
run: make verify
|
||||
|
||||
# Max GH exection time 6H => timeout after that
|
||||
- name: Running performance with big list
|
||||
|
||||
+43
-36
@@ -1,43 +1,50 @@
|
||||
# Misc
|
||||
**/*-cache
|
||||
**/*-config
|
||||
**/.cache
|
||||
*.DS_Store
|
||||
*.exe
|
||||
.devcontainer
|
||||
.gitignore
|
||||
.idea
|
||||
.vscode
|
||||
.devcontainer
|
||||
vendor
|
||||
|
||||
# Binaries
|
||||
**/bindgen
|
||||
**/debug-*
|
||||
**/docgen
|
||||
**/functional-test
|
||||
**/fuzzplayground
|
||||
**/integration-test
|
||||
**/jsdocgen
|
||||
**/main
|
||||
**/memogen
|
||||
**/nuclei
|
||||
**/nuclei-stats*
|
||||
**/nuclei_dev
|
||||
**/nuclei_main
|
||||
**/scan-charts
|
||||
**/scrapefunc
|
||||
**/scrapefuncs
|
||||
**/tsgen
|
||||
|
||||
# Templates
|
||||
/*.yaml
|
||||
|
||||
# Generated docs
|
||||
/dsl.md
|
||||
|
||||
# Junk
|
||||
dist
|
||||
integration_tests/nuclei
|
||||
integration_tests/.cache
|
||||
integration_tests/integration-test
|
||||
cmd/nuclei/main
|
||||
cmd/nuclei/nuclei
|
||||
cmd/integration-test/nuclei
|
||||
cmd/functional-test/nuclei_dev
|
||||
cmd/functional-test/nuclei_main
|
||||
cmd/functional-test/functional-test
|
||||
cmd/docgen/docgen
|
||||
pkg/protocols/common/helpers/deserialization/testdata/Deserialize.class
|
||||
pkg/protocols/common/helpers/deserialization/testdata/ValueObject.class
|
||||
pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser
|
||||
*.exe
|
||||
.gitignore
|
||||
pkg/js/devtools/bindgen/cmd/bindgen/bindgen
|
||||
pkg/js/devtools/jsdocgen/jsdocgen
|
||||
pkg/js/devtools/tsgen/tsgen
|
||||
pkg/js/devtools/tsgen/cmd/tsgen/tsgen
|
||||
*.DS_Store
|
||||
pkg/protocols/headless/engine/.cache
|
||||
/nuclei
|
||||
/bindgen
|
||||
/jsdocgen
|
||||
/tsgen
|
||||
/scrapefuncs
|
||||
/integration_tests/.cache/
|
||||
/*.yaml
|
||||
**/*-config
|
||||
**/*-cache
|
||||
/fuzzplayground
|
||||
integration_tests/fuzzplayground
|
||||
/dsl.md
|
||||
/nuclei-stats
|
||||
/nuclei-stats-*
|
||||
/scan-charts
|
||||
/**/debug-*
|
||||
vendor
|
||||
|
||||
# Headless `screenshot` action
|
||||
*.png
|
||||
|
||||
# Profiling & tracing
|
||||
*.prof
|
||||
*.pprof
|
||||
*.trace
|
||||
@@ -457,26 +457,49 @@ func (template *Template) compileProtocolRequests(options protocols.ExecuterOpti
|
||||
That's it, you've added a new protocol to Nuclei. The next good step would be to write integration tests which are described in `integration-tests` and `cmd/integration-tests` directories.
|
||||
|
||||
|
||||
## Profiling Instructions
|
||||
## Profiling and Tracing
|
||||
|
||||
To enable dumping of Memory profiling data, `-profile-mem` flag can be used along with path to a file. This writes a pprof formatted file which can be used for investigate resource usage with `pprof` tool.
|
||||
To analyze Nuclei's performance and resource usage, you can generate memory profiles and trace files using the `-profile-mem` flag:
|
||||
|
||||
```console
|
||||
$ nuclei -t nuclei-templates/ -u https://example.com -profile-mem mem.pprof
|
||||
```bash
|
||||
nuclei -t nuclei-templates/ -u https://example.com -profile-mem=nuclei-$(git describe --tags)
|
||||
```
|
||||
|
||||
To view profile data in pprof, first install pprof. Then run the below command -
|
||||
This command creates two files:
|
||||
|
||||
```console
|
||||
$ go tool pprof mem.pprof
|
||||
* `nuclei.prof`: Memory (heap) profile
|
||||
* `nuclei.trace`: Execution trace
|
||||
|
||||
### Analyzing the Memory Profile
|
||||
|
||||
1. View the profile in the terminal:
|
||||
|
||||
```bash
|
||||
go tool pprof nuclei.prof
|
||||
```
|
||||
|
||||
To open a web UI on a port to visualize debug data, the below command can be used.
|
||||
2. Display top memory consumers:
|
||||
|
||||
```console
|
||||
$ go tool pprof -http=:8081 mem.pprof
|
||||
```bash
|
||||
go tool pprof -top nuclei.prof | grep "$(go list -m)" | head -10
|
||||
```
|
||||
|
||||
3. Visualize the profile in a web browser:
|
||||
|
||||
```bash
|
||||
go tool pprof -http=:$(shuf -i 1000-99999 -n 1) nuclei.prof
|
||||
```
|
||||
|
||||
### Analyzing the Trace File
|
||||
|
||||
To examine the execution trace:
|
||||
|
||||
```bash
|
||||
go tool trace nuclei.trace
|
||||
```
|
||||
|
||||
These tools help identify performance bottlenecks and memory leaks, allowing for targeted optimizations of Nuclei's codebase.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- [pkg/reporting](./pkg/reporting) - Reporting modules for nuclei.
|
||||
|
||||
+1
-2
@@ -3,13 +3,12 @@ FROM golang:1.21-alpine AS build-env
|
||||
RUN apk add build-base
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN go mod download
|
||||
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,62 +1,128 @@
|
||||
# Go parameters
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOMOD=$(GOCMD) mod
|
||||
GOTEST=$(GOCMD) test
|
||||
GOCMD := go
|
||||
GOBUILD := $(GOCMD) build
|
||||
GOBUILD_OUTPUT :=
|
||||
GOBUILD_PACKAGES :=
|
||||
GOBUILD_ADDITIONAL_ARGS :=
|
||||
GOMOD := $(GOCMD) mod
|
||||
GOTEST := $(GOCMD) test
|
||||
GOFLAGS := -v
|
||||
# This should be disabled if the binary uses pprof
|
||||
LDFLAGS := -s -w
|
||||
|
||||
ifneq ($(shell go env GOOS),darwin)
|
||||
LDFLAGS := -extldflags "-static"
|
||||
LDFLAGS = -extldflags "-static"
|
||||
endif
|
||||
|
||||
.PHONY: all build build-stats scan-charts docs test integration functional tidy devtools jsupdate ts fuzzplayground memogen dsl-docs
|
||||
.PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs
|
||||
.PHONY: devtools-tsgen docs dsl-docs functional fuzzplayground go-build integration
|
||||
.PHONY: jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test tidy ts verify
|
||||
|
||||
all: build
|
||||
build:
|
||||
rm -f nuclei 2>/dev/null
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "nuclei" cmd/nuclei/main.go
|
||||
build-stats:
|
||||
rm -f nuclei-stats 2>/dev/null
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags=stats -o "nuclei-stats" cmd/nuclei/main.go
|
||||
scan-charts:
|
||||
rm -f scan-charts 2>/dev/null
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scan-charts" cmd/scan-charts/main.go
|
||||
|
||||
clean:
|
||||
rm -f '${GOBUILD_OUTPUT}' 2>/dev/null
|
||||
|
||||
go-build: clean
|
||||
go-build:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \
|
||||
-o '${GOBUILD_OUTPUT}' $(GOBUILD_PACKAGES)
|
||||
|
||||
build: GOBUILD_OUTPUT = nuclei
|
||||
build: GOBUILD_PACKAGES = cmd/nuclei/main.go
|
||||
build: go-build
|
||||
|
||||
build-stats: GOBUILD_OUTPUT = nuclei-stats
|
||||
build-stats: GOBUILD_PACKAGES = cmd/nuclei/main.go
|
||||
build-stats: GOBUILD_ADDITIONAL_ARGS = -tags=stats
|
||||
build-stats: go-build
|
||||
|
||||
scan-charts: GOBUILD_OUTPUT = scan-charts
|
||||
scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go
|
||||
scan-charts: go-build
|
||||
|
||||
docs: GOBUILD_OUTPUT = cmd/docgen/docgen
|
||||
docs: GOBUILD_PACKAGES = cmd/docgen/docgen.go
|
||||
docs: bin = dstdocgen
|
||||
docs:
|
||||
if ! which dstdocgen > /dev/null; then
|
||||
echo -e "Command not found! Install? (y/n) \c"
|
||||
go get -v github.com/projectdiscovery/yamldoc-go/cmd/docgen/dstdocgen
|
||||
@if ! which $(bin) >/dev/null; then \
|
||||
read -p "${bin} not found. Do you want to install it? (y/n) " answer; \
|
||||
if [ "$$answer" = "y" ]; then \
|
||||
echo "Installing ${bin}..."; \
|
||||
go get -v github.com/projectdiscovery/yamldoc-go/cmd/docgen/$(bin); \
|
||||
go install -v github.com/projectdiscovery/yamldoc-go/cmd/docgen/$(bin); \
|
||||
else \
|
||||
echo "Please install ${bin} manually."; \
|
||||
exit 1; \
|
||||
fi \
|
||||
fi
|
||||
|
||||
# TODO: Handle the panic, so that we just need to run `go install $(bin)@latest` (line 51-52)
|
||||
$(GOCMD) generate pkg/templates/templates.go
|
||||
$(GOBUILD) -o "cmd/docgen/docgen" cmd/docgen/docgen.go
|
||||
./cmd/docgen/docgen docs.md nuclei-jsonschema.json
|
||||
|
||||
$(GOBUILD) -o "${GOBUILD_OUTPUT}" $(GOBUILD_PACKAGES)
|
||||
./$(GOBUILD_OUTPUT) docs.md nuclei-jsonschema.json
|
||||
|
||||
git reset --hard # line 59
|
||||
|
||||
test: GOFLAGS = -race -v
|
||||
test:
|
||||
$(GOTEST) $(GOFLAGS) ./...
|
||||
|
||||
integration:
|
||||
cd integration_tests; bash run.sh
|
||||
|
||||
functional:
|
||||
cd cmd/functional-test; bash run.sh
|
||||
|
||||
tidy:
|
||||
$(GOMOD) tidy
|
||||
devtools:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "bindgen" pkg/js/devtools/bindgen/cmd/bindgen/main.go
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scrapefuncs" pkg/js/devtools/scrapefuncs/main.go
|
||||
jsupdate:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "bindgen" pkg/js/devtools/bindgen/cmd/bindgen/main.go
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
|
||||
./bindgen -dir pkg/js/libs -out pkg/js/generated
|
||||
./tsgen -dir pkg/js/libs -out pkg/js/generated/ts
|
||||
ts:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
|
||||
./tsgen -dir pkg/js/libs -out pkg/js/generated/ts
|
||||
fuzzplayground:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "fuzzplayground" cmd/tools/fuzzplayground/main.go
|
||||
|
||||
verify: tidy
|
||||
$(GOMOD) verify
|
||||
|
||||
devtools-bindgen: GOBUILD_OUTPUT = bindgen
|
||||
devtools-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go
|
||||
devtools-bindgen: go-build
|
||||
|
||||
devtools-tsgen: GOBUILD_OUTPUT = tsgen
|
||||
devtools-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go
|
||||
devtools-tsgen: go-build
|
||||
|
||||
devtools-scrapefuncs: GOBUILD_OUTPUT = scrapefuncs
|
||||
devtools-scrapefuncs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go
|
||||
devtools-scrapefuncs: go-build
|
||||
|
||||
devtools-all: devtools-bindgen devtools-tsgen devtools-scrapefuncs
|
||||
|
||||
jsupdate-bindgen: GOBUILD_OUTPUT = bindgen
|
||||
jsupdate-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go
|
||||
jsupdate-bindgen: go-build
|
||||
jsupdate-bindgen:
|
||||
./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated
|
||||
|
||||
jsupdate-tsgen: GOBUILD_OUTPUT = tsgen
|
||||
jsupdate-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go
|
||||
jsupdate-tsgen: go-build
|
||||
jsupdate-tsgen:
|
||||
./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated/ts
|
||||
|
||||
jsupdate-all: jsupdate-bindgen jsupdate-tsgen
|
||||
|
||||
ts: jsupdate-tsgen
|
||||
|
||||
fuzzplayground: GOBUILD_OUTPUT = fuzzplayground
|
||||
fuzzplayground: GOBUILD_PACKAGES = cmd/tools/fuzzplayground/main.go
|
||||
fuzzplayground: LDFLAGS = -s -w
|
||||
fuzzplayground: go-build
|
||||
|
||||
memogen: GOBUILD_OUTPUT = memogen
|
||||
memogen: GOBUILD_PACKAGES = cmd/memogen/memogen.go
|
||||
memogen: go-build
|
||||
memogen:
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "memogen" cmd/memogen/memogen.go
|
||||
./memogen -src pkg/js/libs -tpl cmd/memogen/function.tpl
|
||||
./$(GOBUILD_OUTPUT) -src pkg/js/libs -tpl cmd/memogen/function.tpl
|
||||
|
||||
dsl-docs: GOBUILD_OUTPUT = scrapefuncs
|
||||
dsl-docs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go
|
||||
dsl-docs:
|
||||
rm -f dsl.md scrapefuncs 2>/dev/null
|
||||
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scrapefuncs" pkg/js/devtools/scrapefuncs/main.go
|
||||
./scrapefuncs -out dsl.md
|
||||
./$(GOBUILD_OUTPUT) -out dsl.md
|
||||
@@ -179,6 +179,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
-config string path to the nuclei configuration file
|
||||
@@ -310,6 +311,7 @@ STATISTICS:
|
||||
|
||||
CLOUD:
|
||||
-auth configure projectdiscovery cloud (pdcp) api key (default true)
|
||||
-tid, -team-id string upload scan results to given team id (optional) (default "none")
|
||||
-cup, -cloud-upload upload scan results to pdcp dashboard
|
||||
-sid, -scan-id string upload scan results to existing scan id (optional)
|
||||
-sname, -scan-name string scan name to set (optional)
|
||||
|
||||
+50
-3
@@ -1404,6 +1404,19 @@ Valid values:
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>skip-secret-file</code> <i>bool</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SkipSecretFile skips the authentication or authorization configured in the secret file.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>cookie-reuse</code> <i>bool</i>
|
||||
|
||||
</div>
|
||||
@@ -3651,8 +3664,18 @@ Enum Values:
|
||||
|
||||
- <code>files</code>
|
||||
|
||||
- <code>waitdom</code>
|
||||
|
||||
- <code>waitfcp</code>
|
||||
|
||||
- <code>waitfmp</code>
|
||||
|
||||
- <code>waitidle</code>
|
||||
|
||||
- <code>waitload</code>
|
||||
|
||||
- <code>waitstable</code>
|
||||
|
||||
- <code>getresource</code>
|
||||
|
||||
- <code>extract</code>
|
||||
@@ -3669,6 +3692,8 @@ Enum Values:
|
||||
|
||||
- <code>waitevent</code>
|
||||
|
||||
- <code>dialog</code>
|
||||
|
||||
- <code>keyboard</code>
|
||||
|
||||
- <code>debug</code>
|
||||
@@ -3739,11 +3764,33 @@ Appears in:
|
||||
Part Definitions:
|
||||
|
||||
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>response</code> - JSON SSL protocol handshake details
|
||||
- <code>not_after</code> - Timestamp after which the remote cert expires
|
||||
- <code>template-id</code> - ID of the template executed
|
||||
- <code>template-info</code> - Info Block of the template executed
|
||||
- <code>template-path</code> - Path of the template executed
|
||||
- <code>host</code> - Host is the input to the template
|
||||
- <code>port</code> - Port is the port of the host
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>timestamp</code> - Timestamp is the time when the request was made
|
||||
- <code>response</code> - JSON SSL protocol handshake details
|
||||
- <code>cipher</code> - Cipher is the encryption algorithm used
|
||||
- <code>domains</code> - Domains are the list of domain names in the certificate
|
||||
- <code>fingerprint_hash</code> - Fingerprint hash is the unique identifier of the certificate
|
||||
- <code>ip</code> - IP is the IP address of the server
|
||||
- <code>issuer_cn</code> - Issuer CN is the common name of the certificate issuer
|
||||
- <code>issuer_dn</code> - Issuer DN is the distinguished name of the certificate issuer
|
||||
- <code>issuer_org</code> - Issuer organization is the organization of the certificate issuer
|
||||
- <code>not_after</code> - Timestamp after which the remote cert expires
|
||||
- <code>not_before</code> - Timestamp before which the certificate is not valid
|
||||
- <code>probe_status</code> - Probe status indicates if the probe was successful
|
||||
- <code>serial</code> - Serial is the serial number of the certificate
|
||||
- <code>sni</code> - SNI is the server name indication used in the handshake
|
||||
- <code>subject_an</code> - Subject AN is the list of subject alternative names
|
||||
- <code>subject_cn</code> - Subject CN is the common name of the certificate subject
|
||||
- <code>subject_dn</code> - Subject DN is the distinguished name of the certificate subject
|
||||
- <code>subject_org</code> - Subject organization is the organization of the certificate subject
|
||||
- <code>tls_connection</code> - TLS connection is the type of TLS connection used
|
||||
- <code>tls_version</code> - TLS version is the version of the TLS protocol used
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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)
|
||||
expectedError := "no address found for host"
|
||||
if !strings.Contains(event.Error, expectedError) {
|
||||
return fmt.Errorf("unexpected result: expecting \"%s\" error but got \"%s\"", expectedError, event.Error)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
|
||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||
)
|
||||
|
||||
var workflowTestcases = []TestCaseInfo{
|
||||
@@ -25,6 +26,7 @@ var workflowTestcases = []TestCaseInfo{
|
||||
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
|
||||
{Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go
|
||||
{Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}},
|
||||
{Path: "workflow/multimatch-value-share-workflow.yaml", TestCase: &workflowMultiMatchKeyValueShare{}},
|
||||
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
|
||||
}
|
||||
|
||||
@@ -229,6 +231,44 @@ func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {
|
||||
return expectResultsCount(results, 2)
|
||||
}
|
||||
|
||||
type workflowMultiMatchKeyValueShare struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error {
|
||||
var receivedData []string
|
||||
router := httprouter.New()
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
fmt.Fprintf(w, "This is test matcher text")
|
||||
})
|
||||
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v"))
|
||||
})
|
||||
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
receivedData = append(receivedData, string(body))
|
||||
fmt.Fprintf(w, "test-value")
|
||||
})
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we received the data from both request to /path1 and it is not overwritten by the later one.
|
||||
// They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766
|
||||
if !sliceutil.Contains(receivedData, "[test-value-1]") || !sliceutil.Contains(receivedData, "[test-value-2]") {
|
||||
return fmt.Errorf(
|
||||
"incorrect data: did not receive both extracted data from the first request!\nReceived Data:\n\t%s\nResults:\n\t%s",
|
||||
strings.Join(receivedData, "\n\t"),
|
||||
strings.Join(results, "\n\t"),
|
||||
)
|
||||
}
|
||||
// The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one
|
||||
return expectResultsCount(results, 3)
|
||||
}
|
||||
|
||||
type workflowSharedCookies struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
|
||||
@@ -142,4 +142,34 @@
|
||||
# # Username for the elasticsearch instance
|
||||
# username: test
|
||||
# # Password is the password for elasticsearch instance
|
||||
# password: test
|
||||
# password: test
|
||||
#linear:
|
||||
# # api-key is the API key for the linear account
|
||||
# api-key: ""
|
||||
# # allow-list sets a tracker level filter to only create issues for templates with
|
||||
# # these severity labels or tags (does not affect exporters. set those globally)
|
||||
# deny-list:
|
||||
# severity: critical
|
||||
# # deny-list sets a tracker level filter to never create issues for templates with
|
||||
# # these severity labels or tags (does not affect exporters. set those globally)
|
||||
# deny-list:
|
||||
# severity: low
|
||||
# # team-id is the ID of the team in Linear
|
||||
# team-id: ""
|
||||
# # project-id is the ID of the project in Linear
|
||||
# project-id: ""
|
||||
# # duplicate-issue-check flag to enable duplicate tracking issue check
|
||||
# duplicate-issue-check: false
|
||||
# # open-state-id is the ID of the open state in Linear
|
||||
# open-state-id: ""
|
||||
#mongodb:
|
||||
# # the connection string to the MongoDB database
|
||||
# # (e.g., mongodb://root:example@localhost:27017/nuclei?ssl=false&authSource=admin)
|
||||
# connection-string: ""
|
||||
# # the name of the collection to store the issues
|
||||
# collection-name: ""
|
||||
# # excludes the Request and Response from the results (helps with filesize)
|
||||
# omit-raw: false
|
||||
# # determines the number of results to be kept in memory before writing it to the database or 0 to
|
||||
# # persist all in memory and write all results at the end (default)
|
||||
# batch-size: 0
|
||||
+44
-11
@@ -9,9 +9,11 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp"
|
||||
"github.com/projectdiscovery/utils/auth/pdcp"
|
||||
"github.com/projectdiscovery/utils/env"
|
||||
_ "github.com/projectdiscovery/utils/pprof"
|
||||
@@ -102,26 +104,52 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// Profiling related code
|
||||
// Profiling & tracing related code
|
||||
if memProfile != "" {
|
||||
f, err := os.Create(memProfile)
|
||||
memProfile = strings.TrimSuffix(memProfile, filepath.Ext(memProfile)) + ".prof"
|
||||
memProfileFile, err := os.Create(memProfile)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("profile: could not create memory profile %q: %v", memProfile, err)
|
||||
gologger.Fatal().Msgf("profile: could not create memory profile %q file: %v", memProfile, err)
|
||||
}
|
||||
old := runtime.MemProfileRate
|
||||
|
||||
traceFilepath := strings.TrimSuffix(memProfile, filepath.Ext(memProfile)) + ".trace"
|
||||
traceFile, err := os.Create(traceFilepath)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("profile: could not create trace %q file: %v", traceFilepath, err)
|
||||
}
|
||||
|
||||
oldMemProfileRate := runtime.MemProfileRate
|
||||
runtime.MemProfileRate = 4096
|
||||
gologger.Print().Msgf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, memProfile)
|
||||
|
||||
// Start tracing
|
||||
if err := trace.Start(traceFile); err != nil {
|
||||
gologger.Fatal().Msgf("profile: could not start trace: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = pprof.Lookup("heap").WriteTo(f, 0)
|
||||
f.Close()
|
||||
runtime.MemProfileRate = old
|
||||
gologger.Print().Msgf("profile: memory profiling disabled, %s", memProfile)
|
||||
// Start CPU profiling
|
||||
if err := pprof.WriteHeapProfile(memProfileFile); err != nil {
|
||||
gologger.Fatal().Msgf("profile: could not start CPU profile: %v", err)
|
||||
}
|
||||
memProfileFile.Close()
|
||||
traceFile.Close()
|
||||
trace.Stop()
|
||||
runtime.MemProfileRate = oldMemProfileRate
|
||||
|
||||
gologger.Info().Msgf("Memory profile saved at %q", memProfile)
|
||||
gologger.Info().Msgf("Traced at %q", traceFilepath)
|
||||
}()
|
||||
}
|
||||
|
||||
runner.ParseOptions(options)
|
||||
|
||||
if options.ScanUploadFile != "" {
|
||||
if err := runner.UploadResultsToCloud(options); err != nil {
|
||||
gologger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
nucleiRunner, err := runner.New(options)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not create runner: %s\n", err)
|
||||
@@ -272,6 +300,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.StringVarP(&options.SarifExport, "sarif-export", "se", "", "file to export results in SARIF format"),
|
||||
flagSet.StringVarP(&options.JSONExport, "json-export", "je", "", "file to export results in JSON format"),
|
||||
flagSet.StringVarP(&options.JSONLExport, "jsonl-export", "jle", "", "file to export results in JSONL(ine) format"),
|
||||
flagSet.StringSliceVarP(&options.Redact, "redact", "rd", nil, "redact given list of keys from query parameter, request header and body", goflags.CommaSeparatedStringSliceOptions),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("configs", "Configurations",
|
||||
@@ -287,6 +316,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.RuntimeMapVarP(&options.Vars, "var", "V", nil, "custom vars in key=value format"),
|
||||
flagSet.StringVarP(&options.ResolversFile, "resolvers", "r", "", "file containing resolver list for nuclei"),
|
||||
flagSet.BoolVarP(&options.SystemResolvers, "system-resolvers", "sr", false, "use system DNS resolving as error fallback"),
|
||||
flagSet.BoolVarP(&options.DisableDefaultResolvers, "disable-default-resolvers", "dfr", false, "disable default resolvers"),
|
||||
flagSet.BoolVarP(&options.DisableClustering, "disable-clustering", "dc", false, "disable clustering of requests"),
|
||||
flagSet.BoolVar(&options.OfflineHTTP, "passive", false, "enable passive HTTP response processing mode"),
|
||||
flagSet.BoolVarP(&options.ForceAttemptHTTP2, "force-http2", "fh2", false, "force http2 connection on requests"),
|
||||
@@ -393,7 +423,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.CallbackVar(printVersion, "version", "show nuclei version"),
|
||||
flagSet.BoolVarP(&options.HangMonitor, "hang-monitor", "hm", false, "enable nuclei hang monitoring"),
|
||||
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
|
||||
flagSet.StringVar(&memProfile, "profile-mem", "", "optional nuclei memory profile dump file"),
|
||||
flagSet.StringVar(&memProfile, "profile-mem", "", "generate memory (heap) profile & trace files"),
|
||||
flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"),
|
||||
flagSet.BoolVarP(&options.ShowVarDump, "show-var-dump", "svd", false, "show variables dump for debugging"),
|
||||
flagSet.BoolVarP(&options.EnablePprof, "enable-pprof", "ep", false, "enable pprof debugging server"),
|
||||
@@ -417,9 +447,12 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
|
||||
flagSet.CreateGroup("cloud", "Cloud",
|
||||
flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
|
||||
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard"),
|
||||
flagSet.StringVarP(&options.TeamID, "team-id", "tid", _pdcp.TeamIDEnv, "upload scan results to given team id (optional)"),
|
||||
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard [DEPRECATED use -dashboard]"),
|
||||
flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to existing scan id (optional)"),
|
||||
flagSet.StringVarP(&options.ScanName, "scan-name", "sname", "", "scan name to set (optional)"),
|
||||
flagSet.BoolVarP(&options.EnableCloudUpload, "dashboard", "pd", false, "upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard"),
|
||||
flagSet.StringVarP(&options.ScanUploadFile, "dashboard-upload", "pdu", "", "upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("Authentication", "Authentication",
|
||||
|
||||
+5
-5
@@ -162,7 +162,7 @@ func process(opts options) error {
|
||||
var updated bool // if max-requests is updated
|
||||
dataString, updated, err = parseAndAddMaxRequests(templateCatalog, path, dataString)
|
||||
if err != nil {
|
||||
gologger.Info().Label("max-request").Msgf(logErrMsg(path, err, opts.debug, errFile))
|
||||
gologger.Info().Label("max-request").Msg(logErrMsg(path, err, opts.debug, errFile))
|
||||
} else {
|
||||
if updated {
|
||||
gologger.Info().Label("max-request").Msgf("✅ updated template: %s\n", path)
|
||||
@@ -255,7 +255,7 @@ func enhanceTemplate(data string) (string, bool, error) {
|
||||
return data, false, errorutil.New("validation failed").WithTag("validate")
|
||||
}
|
||||
if templateResp.Error.Name != "" {
|
||||
return data, false, errorutil.New(templateResp.Error.Name)
|
||||
return data, false, errorutil.New("%s", templateResp.Error.Name)
|
||||
}
|
||||
if strings.TrimSpace(templateResp.Enhanced) == "" && !templateResp.Lint {
|
||||
if templateResp.LintError.Reason != "" {
|
||||
@@ -289,7 +289,7 @@ func formatTemplate(data string) (string, bool, error) {
|
||||
return data, false, errorutil.New("validation failed").WithTag("validate")
|
||||
}
|
||||
if templateResp.Error.Name != "" {
|
||||
return data, false, errorutil.New(templateResp.Error.Name)
|
||||
return data, false, errorutil.New("%s", templateResp.Error.Name)
|
||||
}
|
||||
if strings.TrimSpace(templateResp.Updated) == "" && !templateResp.Lint {
|
||||
if templateResp.LintError.Reason != "" {
|
||||
@@ -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 {
|
||||
@@ -345,7 +345,7 @@ func validateTemplate(data string) (bool, error) {
|
||||
return false, errorutil.New("validation failed").WithTag("validate")
|
||||
}
|
||||
if validateResp.Error.Name != "" {
|
||||
return false, errorutil.New(validateResp.Error.Name)
|
||||
return false, errorutil.New("%s", validateResp.Error.Name)
|
||||
}
|
||||
return false, errorutil.New("template validation failed")
|
||||
}
|
||||
|
||||
+4
-4
@@ -21,9 +21,9 @@ WORKFLOW="Build Test"
|
||||
GREP_ERROR_PATTERN='Test "http/interactsh.yaml" failed'
|
||||
|
||||
#Set fonts for Help.
|
||||
NORM=`tput sgr0`
|
||||
BOLD=`tput bold`
|
||||
REV=`tput smso`
|
||||
NORM=$(tput sgr0)
|
||||
BOLD=$(tput bold)
|
||||
REV=$(tput smso)
|
||||
|
||||
HELP()
|
||||
{
|
||||
@@ -73,7 +73,7 @@ function print_bold() {
|
||||
function retry_failed_jobs() {
|
||||
print_bold "Checking failed workflows for branch $BRANCH before $BEFORE"
|
||||
|
||||
date=`date +%Y-%m-%d'T'%H:%M'Z' -d "$BEFORE"`
|
||||
date=$(date +%Y-%m-%d'T'%H:%M'Z' -d "$BEFORE")
|
||||
|
||||
workflowIds=$(gh run list --limit "$LIMIT" --json headBranch,status,name,conclusion,databaseId,updatedAt | jq -c '.[] |
|
||||
select ( .headBranch==$branch ) |
|
||||
|
||||
@@ -19,13 +19,13 @@ require (
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.0.20
|
||||
github.com/projectdiscovery/fastdialer v0.2.3
|
||||
github.com/projectdiscovery/hmap v0.0.54
|
||||
github.com/projectdiscovery/clistats v0.1.1
|
||||
github.com/projectdiscovery/fastdialer v0.2.9
|
||||
github.com/projectdiscovery/hmap v0.0.59
|
||||
github.com/projectdiscovery/interactsh v1.2.0
|
||||
github.com/projectdiscovery/rawhttp v0.1.60
|
||||
github.com/projectdiscovery/retryabledns v1.0.69
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.71
|
||||
github.com/projectdiscovery/rawhttp v0.1.67
|
||||
github.com/projectdiscovery/retryabledns v1.0.77
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.78
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.4
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.5.0
|
||||
@@ -36,11 +36,11 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/weppos/publicsuffix-go v0.30.2
|
||||
github.com/xanzy/go-gitlab v0.84.0
|
||||
github.com/xanzy/go-gitlab v0.107.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/text v0.18.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@@ -59,18 +59,20 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/charmbracelet/glamour v0.6.0
|
||||
github.com/charmbracelet/glamour v0.8.0
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/getkin/kin-openapi v0.123.0
|
||||
github.com/getkin/kin-openapi v0.126.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.5
|
||||
github.com/go-pg/pg v8.0.7+incompatible
|
||||
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.3
|
||||
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
|
||||
@@ -79,30 +81,32 @@ 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.62
|
||||
github.com/projectdiscovery/gologger v1.1.18
|
||||
github.com/projectdiscovery/goflags v0.1.64
|
||||
github.com/projectdiscovery/gologger v1.1.24
|
||||
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.56
|
||||
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.4
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.14
|
||||
github.com/projectdiscovery/useragent v0.0.71
|
||||
github.com/projectdiscovery/utils v0.2.11
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.18
|
||||
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
|
||||
golang.org/x/term v0.21.0
|
||||
go.mongodb.org/mongo-driver v1.17.0
|
||||
golang.org/x/term v0.24.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
@@ -117,6 +121,7 @@ require (
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect
|
||||
@@ -131,6 +136,8 @@ require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.13.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.3.2 // indirect
|
||||
github.com/cheggaaa/pb/v3 v3.1.4 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||
@@ -139,7 +146,7 @@ require (
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/docker/cli v24.0.5+incompatible // indirect
|
||||
github.com/docker/docker v24.0.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
@@ -152,8 +159,8 @@ require (
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.9 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -168,7 +175,6 @@ require (
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
@@ -190,11 +196,12 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.12 // indirect
|
||||
github.com/opencontainers/runc v1.1.14 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
@@ -202,7 +209,7 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/projectdiscovery/asnmap v1.1.1 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.1.0 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.5 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.6 // indirect
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
|
||||
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
@@ -223,18 +230,22 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||
github.com/ysmood/got v0.34.1 // indirect
|
||||
github.com/yuin/goldmark v1.5.4 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.4 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.3 // indirect
|
||||
github.com/zcalusic/sysinfo v1.0.2 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/goleak v1.3.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
)
|
||||
@@ -262,7 +273,6 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -277,16 +287,16 @@ require (
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||
github.com/projectdiscovery/networkpolicy v0.0.9
|
||||
github.com/rivo/uniseg v0.4.6 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
@@ -302,14 +312,13 @@ require (
|
||||
go.etcd.io/bbolt v1.3.10 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
goftp.io/server/v2 v2.0.1 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -93,9 +93,15 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=
|
||||
github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -174,9 +180,10 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
@@ -217,8 +224,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
|
||||
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
|
||||
github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
|
||||
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
|
||||
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
|
||||
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
@@ -271,8 +284,8 @@ github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
|
||||
github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
|
||||
@@ -329,8 +342,8 @@ github.com/gaissmai/bart v0.9.5 h1:vy+r4Px6bjZ+v2QYXAsg63vpz9IfzdW146A8Cn4GPIo=
|
||||
github.com/gaissmai/bart v0.9.5/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
|
||||
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY=
|
||||
github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
@@ -369,10 +382,10 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||
github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
|
||||
github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g=
|
||||
github.com/go-pg/pg v8.0.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -453,8 +466,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@@ -510,7 +521,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@@ -561,6 +571,8 @@ github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
|
||||
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
|
||||
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -572,8 +584,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
|
||||
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
|
||||
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU=
|
||||
github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4=
|
||||
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
|
||||
@@ -627,6 +639,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.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=
|
||||
github.com/kitabisa/go-ci v1.0.3/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=
|
||||
@@ -695,9 +709,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -708,9 +721,8 @@ github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1w
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
|
||||
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@@ -743,13 +755,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
@@ -781,8 +794,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
|
||||
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@@ -830,30 +843,30 @@ github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k
|
||||
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
|
||||
github.com/projectdiscovery/cdncheck v1.1.0 h1:qDITidmJsejzpk3rMkauCh6sjI2GH9hW/snk0cQ3kXE=
|
||||
github.com/projectdiscovery/cdncheck v1.1.0/go.mod h1:sZ8U4MjHSsyaTVjBbYWHT1cwUVvUYwDX1W+WvWRicIc=
|
||||
github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPooH+DGMgoWq4=
|
||||
github.com/projectdiscovery/clistats v0.0.20/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.3 h1:K03x5XEXGyVWxS2rtSR104E9kHF0aphN7kOCzbh8zv0=
|
||||
github.com/projectdiscovery/fastdialer v0.2.3/go.mod h1:a0BKvETrO1EAohUYp9gwtbbce0hKD1qGrTegyAUqyRo=
|
||||
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
|
||||
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
|
||||
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.9 h1:vDCqxVMCyUu3oVEizEK1K8K+CCcLkVDW3X2HfiWaVFA=
|
||||
github.com/projectdiscovery/fastdialer v0.2.9/go.mod h1:mYv5QaNBDDSHlZO9DI0niRMw+G5hUzwIhs8QixSElUI=
|
||||
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/freeport v0.0.6 h1:ROqzuXN8JPqkGdBueb3ah691nS2g2p7r3/3x2E33GbI=
|
||||
github.com/projectdiscovery/freeport v0.0.6/go.mod h1:T2kIy+WrbyxBIhI8V3Y9aeNGnuhnM8tEUSK/cm9GjAg=
|
||||
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.62 h1:UmzKJQT+1UyqT1cZDmb3vZ8/IGhQ7LTsWfdqVcAGoJc=
|
||||
github.com/projectdiscovery/goflags v0.1.62/go.mod h1:d1/D8GaTDoV332ABwceUcY1ffKODaYFlGP0Oriq3wfk=
|
||||
github.com/projectdiscovery/gologger v1.1.18 h1:fux2S/LXYe18/w4PylAFpEFg+EApNCuyS85x25nDE98=
|
||||
github.com/projectdiscovery/gologger v1.1.18/go.mod h1:IgjKgVLfVGUovxlC6WJcXK0qvo/tNZ1P0+r6miQqyRk=
|
||||
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.24 h1:TmA4k9sA6ZvfyRnprZKQ0Uq34g//u5R9yTDPL9IzTOQ=
|
||||
github.com/projectdiscovery/gologger v1.1.24/go.mod h1:JA0JMJ+ply+J2wD062TN4h85thm6/28jAlrntwccKVU=
|
||||
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.54 h1:b3pdQZwCw4is3xiL2jBx7SJZcYaf/7vtozY7bjUzO/s=
|
||||
github.com/projectdiscovery/hmap v0.0.54/go.mod h1:j0oakxYOWEfk29wRq5gQgrCv1JnfAfzGaMsRWwEas80=
|
||||
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.59 h1:xWCr/GY2QJanFzwKydh/EkGdOKM4iAcN9hQvvCMgO6A=
|
||||
github.com/projectdiscovery/hmap v0.0.59/go.mod h1:uHhhnPmvq9qXvCjBSQXCBAlmA1r8JGufP775IkBSbgs=
|
||||
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=
|
||||
@@ -866,30 +879,30 @@ 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.56 h1:WliU7NvfMb5hK/IJjOFlWIXU1G7+QRylMhSybaSCTI8=
|
||||
github.com/projectdiscovery/ratelimit v0.0.56/go.mod h1:GbnAo+MbB4R/4jiOI1mH4KAJfovmrPnq4NElcI99fvs=
|
||||
github.com/projectdiscovery/rawhttp v0.1.67 h1:HYzathMk3c8Y83hYjHM4GCBFbz/G+vABe0Lz6ajaowY=
|
||||
github.com/projectdiscovery/rawhttp v0.1.67/go.mod h1:5viJ6odzc9ZuEFppj/E7HdX+u99FoYlvXnhHyTNc7N0=
|
||||
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.69 h1:6vhhlfxPQoJI0j1enYSzyagyAZJNfGSg9TNloVsxEBQ=
|
||||
github.com/projectdiscovery/retryabledns v1.0.69/go.mod h1:5SDuGtXgL4W1z/9V+xnmHFYg03Fi1Ud/F2kqlPlAgfE=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.71 h1:yXPNShCOwoTz7dBSJsBhBh4g4ujX62XS/BrH/fL1VyE=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.71/go.mod h1:wY3T89EwcCKAw6iyMDvwzGPyL3d8TaBU80hnDErEKgM=
|
||||
github.com/projectdiscovery/retryabledns v1.0.77 h1:rCFSiTPNI7h9PP1uUvrmcq/6XVZVdxpha2H1ioArKpk=
|
||||
github.com/projectdiscovery/retryabledns v1.0.77/go.mod h1:ce1JTjAOxLujqITtA5VLlbhLRVubx+GETYasivapai4=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.78 h1:If7/XjCWk893YrnTMaW69TNMsfE1Er3i1SWOkWbEk4E=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.78/go.mod h1:NqzTdnGihSRkF9c/aXo+3qTJXEeNwnOuza6GrlPo9qw=
|
||||
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.4 h1:CHnlt2la4jr8TeL7ZK7UhQItHY7DDXqIuLnnxyAJLDY=
|
||||
github.com/projectdiscovery/utils v0.2.4/go.mod h1:2Vx7geSrBfCPqknZywqbChQm8SE30mcyrlB5YsxEnhA=
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.14 h1:nt1IM4RUmqeymsXk4h6BsZbKDoS2hjFvPkT2GaI1rz4=
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.14/go.mod h1:/hzgxkBFTMe2wDbA93nFfoMjULw7/vIZ9QPSAnCgUa8=
|
||||
github.com/projectdiscovery/useragent v0.0.71 h1:Q02L3LV15ztOQ6FfmVSqVmOd5QhvzI+yAgYOc/32Nvg=
|
||||
github.com/projectdiscovery/useragent v0.0.71/go.mod h1:DHPruFLCvCvkd2qqPwwQZrP9sziv0lxQJ0R1rE1fa8E=
|
||||
github.com/projectdiscovery/utils v0.2.11 h1:TO7fBG5QI256sn1YuTD87yn4+4OjGJ2wT1772uEnp4Q=
|
||||
github.com/projectdiscovery/utils v0.2.11/go.mod h1:W0E74DWkKxlcyKS5XwcAwiob7+smoszPPi1NgX3vZyk=
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.18 h1:fFgETis0HcsNE7wREaUPYP45JqIyHgGorJaVp1RH7g4=
|
||||
github.com/projectdiscovery/wappalyzergo v0.1.18/go.mod h1:/hzgxkBFTMe2wDbA93nFfoMjULw7/vIZ9QPSAnCgUa8=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.4/go.mod h1:8PIPRcUD55UbtQdcfFR1hpIGRWG0P7alClXNGt1TBik=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -933,8 +946,8 @@ github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7
|
||||
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
@@ -964,6 +977,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=
|
||||
@@ -1068,10 +1083,16 @@ github.com/weppos/publicsuffix-go v0.30.2/go.mod h1:/hGscit36Yt+wammfBBwdMdxBT8b
|
||||
github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xanzy/go-gitlab v0.84.0 h1:PdpCaskQSgcVDsx21c6ikf8Rfyo7SNtFAJwP9PrbCFE=
|
||||
github.com/xanzy/go-gitlab v0.84.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
|
||||
github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd2Y=
|
||||
github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -1085,6 +1106,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
|
||||
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
|
||||
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
|
||||
@@ -1104,11 +1127,11 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
|
||||
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
|
||||
github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc=
|
||||
@@ -1137,6 +1160,8 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k=
|
||||
go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1185,8 +1210,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1270,7 +1295,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
@@ -1280,8 +1304,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1291,8 +1315,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1306,8 +1330,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1370,7 +1394,6 @@ golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1383,8 +1406,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -1397,8 +1420,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1415,14 +1438,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1505,8 +1528,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -1569,8 +1590,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1604,7 +1626,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
id: multimatch-value-share-template
|
||||
|
||||
info:
|
||||
name: MultiMatch Value Share Template
|
||||
author: tovask
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- path:
|
||||
- "{{BaseURL}}/path1?v=1"
|
||||
- "{{BaseURL}}/path1?v=2"
|
||||
matchers:
|
||||
- type: word
|
||||
name: test-matcher
|
||||
words:
|
||||
- "href"
|
||||
extractors:
|
||||
- type: regex
|
||||
part: body
|
||||
name: extracted
|
||||
regex:
|
||||
- 'href="(.*)"'
|
||||
group: 1
|
||||
@@ -0,0 +1,21 @@
|
||||
id: multimatch-value-share-workflow
|
||||
|
||||
info:
|
||||
name: MultiMatch Value Share Workflow
|
||||
author: tovask
|
||||
severity: info
|
||||
description: Workflow to test value sharing when multiple matches occur in the extractor template
|
||||
|
||||
workflows:
|
||||
- template: workflow/multimatch-value-share-template.yaml
|
||||
subtemplates:
|
||||
- template: workflow/match-1.yaml
|
||||
subtemplates:
|
||||
- template: workflow/http-value-share-template-2.yaml
|
||||
- template: workflow/multimatch-value-share-template.yaml
|
||||
matchers:
|
||||
- name: test-matcher
|
||||
subtemplates:
|
||||
- template: workflow/match-1.yaml
|
||||
subtemplates:
|
||||
- template: workflow/http-value-share-template-2.yaml
|
||||
@@ -5,9 +5,17 @@ import (
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
)
|
||||
|
||||
func getScanDashBoardURL(id string) string {
|
||||
func getScanDashBoardURL(id string, teamID string) string {
|
||||
ux, _ := urlutil.Parse(pdcpauth.DashBoardURL)
|
||||
ux.Path = "/scans/" + id
|
||||
if ux.Params == nil {
|
||||
ux.Params = urlutil.NewOrderedParams()
|
||||
}
|
||||
if teamID != "" {
|
||||
ux.Params.Add("team_id", teamID)
|
||||
} else {
|
||||
ux.Params.Add("team_id", NoneTeamID)
|
||||
}
|
||||
ux.Update()
|
||||
return ux.String()
|
||||
}
|
||||
|
||||
+18
-7
@@ -32,13 +32,14 @@ const (
|
||||
MaxChunkSize = 4 * unitutils.Mega // 4 MB
|
||||
xidRe = `^[a-z0-9]{20}$`
|
||||
teamIDHeader = "X-Team-Id"
|
||||
NoneTeamID = "none"
|
||||
)
|
||||
|
||||
var (
|
||||
xidRegex = regexp.MustCompile(xidRe)
|
||||
_ output.Writer = &UploadWriter{}
|
||||
// teamID if given
|
||||
teamID = env.GetEnvOrDefault("PDCP_TEAM_ID", "")
|
||||
TeamIDEnv = env.GetEnvOrDefault("PDCP_TEAM_ID", NoneTeamID)
|
||||
)
|
||||
|
||||
// UploadWriter is a writer that uploads its output to pdcp
|
||||
@@ -53,6 +54,7 @@ type UploadWriter struct {
|
||||
scanID string
|
||||
scanName string
|
||||
counter atomic.Int32
|
||||
TeamID string
|
||||
}
|
||||
|
||||
// NewUploadWriter creates a new upload writer
|
||||
@@ -61,8 +63,9 @@ func NewUploadWriter(ctx context.Context, creds *pdcpauth.PDCPCredentials) (*Upl
|
||||
return nil, fmt.Errorf("no credentials provided")
|
||||
}
|
||||
u := &UploadWriter{
|
||||
creds: creds,
|
||||
done: make(chan struct{}, 1),
|
||||
creds: creds,
|
||||
done: make(chan struct{}, 1),
|
||||
TeamID: NoneTeamID,
|
||||
}
|
||||
var err error
|
||||
reader, writer := io.Pipe()
|
||||
@@ -110,6 +113,14 @@ func (u *UploadWriter) SetScanName(name string) {
|
||||
u.scanName = name
|
||||
}
|
||||
|
||||
func (u *UploadWriter) SetTeamID(id string) {
|
||||
if id == "" {
|
||||
u.TeamID = NoneTeamID
|
||||
} else {
|
||||
u.TeamID = id
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) {
|
||||
reader := bufio.NewReader(r)
|
||||
ch := make(chan string, 4)
|
||||
@@ -136,7 +147,7 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) {
|
||||
if u.scanID == "" {
|
||||
gologger.Verbose().Msgf("Scan results upload to cloud skipped, no results found to upload")
|
||||
} else {
|
||||
gologger.Info().Msgf("%v Scan results uploaded to cloud, you can view scan results at %v", u.counter.Load(), getScanDashBoardURL(u.scanID))
|
||||
gologger.Info().Msgf("%v Scan results uploaded to cloud, you can view scan results at %v", u.counter.Load(), getScanDashBoardURL(u.scanID, u.TeamID))
|
||||
}
|
||||
}()
|
||||
// temporary buffer to store the results
|
||||
@@ -189,7 +200,7 @@ func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error {
|
||||
// if successful, reset the buffer
|
||||
buff.Reset()
|
||||
// log in verbose mode
|
||||
gologger.Warning().Msgf("Uploaded results chunk, you can view scan results at %v", getScanDashBoardURL(u.scanID))
|
||||
gologger.Warning().Msgf("Uploaded results chunk, you can view scan results at %v", getScanDashBoardURL(u.scanID, u.TeamID))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -248,8 +259,8 @@ func (u *UploadWriter) getRequest(bin []byte) (*retryablehttp.Request, error) {
|
||||
req.URL.Update()
|
||||
|
||||
req.Header.Set(pdcpauth.ApiKeyHeaderName, u.creds.APIKey)
|
||||
if teamID != "" {
|
||||
req.Header.Set(teamIDHeader, teamID)
|
||||
if u.TeamID != NoneTeamID && u.TeamID != "" {
|
||||
req.Header.Set(teamIDHeader, u.TeamID)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
@@ -51,6 +51,7 @@ func GetAuthTmplStore(opts types.Options, catalog catalog.Catalog, execOpts prot
|
||||
opts.ExcludeProtocols = nil
|
||||
opts.IncludeConditions = nil
|
||||
cfg := loader.NewConfig(&opts, catalog, execOpts)
|
||||
cfg.StoreId = loader.AuthStoreId
|
||||
store, err := loader.New(cfg)
|
||||
if err != nil {
|
||||
return nil, errorutil.NewWithErr(err).Msgf("failed to initialize dynamic auth templates store")
|
||||
|
||||
@@ -426,6 +426,9 @@ func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer {
|
||||
if r.options.ScanName != "" {
|
||||
uploadWriter.SetScanName(r.options.ScanName)
|
||||
}
|
||||
if r.options.TeamID != "" {
|
||||
uploadWriter.SetTeamID(r.options.TeamID)
|
||||
}
|
||||
return output.NewMultiWriter(writer, uploadWriter)
|
||||
}
|
||||
|
||||
@@ -498,8 +501,17 @@ func (r *Runner) RunEnumeration() error {
|
||||
}
|
||||
|
||||
if r.options.ShouldUseHostError() {
|
||||
cache := hosterrorscache.New(r.options.MaxHostError, hosterrorscache.DefaultMaxHostsCount, r.options.TrackError)
|
||||
maxHostError := r.options.MaxHostError
|
||||
if r.options.TemplateThreads > maxHostError {
|
||||
gologger.Print().Msgf("[%v] The concurrency value is higher than max-host-error", r.colorizer.BrightYellow("WRN"))
|
||||
gologger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", r.options.TemplateThreads)
|
||||
|
||||
maxHostError = r.options.TemplateThreads
|
||||
}
|
||||
|
||||
cache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, r.options.TrackError)
|
||||
cache.SetVerbose(r.options.Verbose)
|
||||
|
||||
r.hostErrors = cache
|
||||
executorOpts.HostErrorsCache = cache
|
||||
}
|
||||
@@ -781,6 +793,52 @@ func (r *Runner) SaveResumeConfig(path string) error {
|
||||
return os.WriteFile(path, data, permissionutil.ConfigFilePermission)
|
||||
}
|
||||
|
||||
// upload existing scan results to cloud with progress
|
||||
func UploadResultsToCloud(options *types.Options) error {
|
||||
h := &pdcpauth.PDCPCredHandler{}
|
||||
creds, err := h.GetCreds()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get credentials for cloud upload")
|
||||
}
|
||||
ctx := context.TODO()
|
||||
uploadWriter, err := pdcp.NewUploadWriter(ctx, creds)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create upload writer")
|
||||
}
|
||||
if options.ScanID != "" {
|
||||
_ = uploadWriter.SetScanID(options.ScanID)
|
||||
}
|
||||
if options.ScanName != "" {
|
||||
uploadWriter.SetScanName(options.ScanName)
|
||||
}
|
||||
if options.TeamID != "" {
|
||||
uploadWriter.SetTeamID(options.TeamID)
|
||||
}
|
||||
|
||||
// Open file to count the number of results first
|
||||
file, err := os.Open(options.ScanUploadFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not open scan upload file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gologger.Info().Msgf("Uploading scan results to cloud dashboard from %s", options.ScanUploadFile)
|
||||
dec := json.NewDecoder(file)
|
||||
for dec.More() {
|
||||
var r output.ResultEvent
|
||||
err := dec.Decode(&r)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("Could not decode jsonl: %s\n", err)
|
||||
continue
|
||||
}
|
||||
if err = uploadWriter.Write(&r); err != nil {
|
||||
gologger.Warning().Msgf("[%s] failed to upload: %s\n", r.TemplateID, err)
|
||||
}
|
||||
}
|
||||
uploadWriter.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
type WalkFunc func(reflect.Value, reflect.StructField)
|
||||
|
||||
// Walk traverses a struct and executes a callback function on each value in the struct.
|
||||
|
||||
+1
-1
@@ -82,5 +82,5 @@ func TestMain(m *testing.M) {
|
||||
// no need to run this test on github actions
|
||||
return
|
||||
}
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
+2
-1
@@ -178,7 +178,8 @@ func (e *NucleiEngine) SignTemplate(tmplSigner *signer.TemplateSigner, data []by
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
buff := bytes.NewBuffer(signer.RemoveSignatureFromData(data))
|
||||
_, content := signer.ExtractSignatureAndContent(data)
|
||||
buff := bytes.NewBuffer(content)
|
||||
buff.WriteString("\n" + signatureData)
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
+8
-8
@@ -108,14 +108,6 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.opts.ProxyInternal && types.ProxyURL != "" || types.ProxySocksURL != "" {
|
||||
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.httpClient = httpclient
|
||||
}
|
||||
|
||||
e.parser = templates.NewParser()
|
||||
|
||||
if sharedInit == nil || protocolstate.ShouldInit() {
|
||||
@@ -126,6 +118,14 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
_ = protocolinit.Init(e.opts)
|
||||
})
|
||||
|
||||
if e.opts.ProxyInternal && types.ProxyURL != "" || types.ProxySocksURL != "" {
|
||||
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.httpClient = httpclient
|
||||
}
|
||||
|
||||
e.applyRequiredDefaults(ctx)
|
||||
var err error
|
||||
|
||||
|
||||
@@ -247,7 +247,12 @@
|
||||
"time",
|
||||
"select",
|
||||
"files",
|
||||
"waitdom",
|
||||
"waitfcp",
|
||||
"waitfmp",
|
||||
"waitidle",
|
||||
"waitload",
|
||||
"waitstable",
|
||||
"getresource",
|
||||
"extract",
|
||||
"setmethod",
|
||||
@@ -256,6 +261,7 @@
|
||||
"deleteheader",
|
||||
"setbody",
|
||||
"waitevent",
|
||||
"waitdialog",
|
||||
"keyboard",
|
||||
"debug",
|
||||
"sleep",
|
||||
@@ -787,6 +793,11 @@
|
||||
"title": "signature is the http request signature method",
|
||||
"description": "Signature is the HTTP Request signature Method"
|
||||
},
|
||||
"skip-secret-file": {
|
||||
"type": "boolean",
|
||||
"title": "bypass secret file",
|
||||
"description": "Skips the authentication or authorization configured in the secret file"
|
||||
},
|
||||
"cookie-reuse": {
|
||||
"type": "boolean",
|
||||
"title": "optional cookie reuse enable",
|
||||
|
||||
+47
-31
@@ -16,8 +16,8 @@ import (
|
||||
type FileAuthProvider struct {
|
||||
Path string
|
||||
store *authx.Authx
|
||||
compiled map[*regexp.Regexp]authx.AuthStrategy
|
||||
domains map[string]authx.AuthStrategy
|
||||
compiled map[*regexp.Regexp][]authx.AuthStrategy
|
||||
domains map[string][]authx.AuthStrategy
|
||||
}
|
||||
|
||||
// NewFileAuthProvider creates a new file based auth provider
|
||||
@@ -56,25 +56,31 @@ func (f *FileAuthProvider) init() {
|
||||
if len(secret.DomainsRegex) > 0 {
|
||||
for _, domain := range secret.DomainsRegex {
|
||||
if f.compiled == nil {
|
||||
f.compiled = make(map[*regexp.Regexp]authx.AuthStrategy)
|
||||
f.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy)
|
||||
}
|
||||
compiled, err := regexp.Compile(domain)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
f.compiled[compiled] = secret.GetStrategy()
|
||||
|
||||
if ss, ok := f.compiled[compiled]; ok {
|
||||
f.compiled[compiled] = append(ss, secret.GetStrategy())
|
||||
} else {
|
||||
f.compiled[compiled] = []authx.AuthStrategy{secret.GetStrategy()}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, domain := range secret.Domains {
|
||||
if f.domains == nil {
|
||||
f.domains = make(map[string]authx.AuthStrategy)
|
||||
f.domains = make(map[string][]authx.AuthStrategy)
|
||||
}
|
||||
f.domains[strings.TrimSpace(domain)] = secret.GetStrategy()
|
||||
if strings.HasSuffix(domain, ":80") {
|
||||
f.domains[strings.TrimSuffix(domain, ":80")] = secret.GetStrategy()
|
||||
}
|
||||
if strings.HasSuffix(domain, ":443") {
|
||||
f.domains[strings.TrimSuffix(domain, ":443")] = secret.GetStrategy()
|
||||
domain = strings.TrimSpace(domain)
|
||||
domain = strings.TrimSuffix(domain, ":80")
|
||||
domain = strings.TrimSuffix(domain, ":443")
|
||||
if ss, ok := f.domains[domain]; ok {
|
||||
f.domains[domain] = append(ss, secret.GetStrategy())
|
||||
} else {
|
||||
f.domains[domain] = []authx.AuthStrategy{secret.GetStrategy()}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,32 +88,38 @@ func (f *FileAuthProvider) init() {
|
||||
if len(dynamic.DomainsRegex) > 0 {
|
||||
for _, domain := range dynamic.DomainsRegex {
|
||||
if f.compiled == nil {
|
||||
f.compiled = make(map[*regexp.Regexp]authx.AuthStrategy)
|
||||
f.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy)
|
||||
}
|
||||
compiled, err := regexp.Compile(domain)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
f.compiled[compiled] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
|
||||
if ss, ok := f.compiled[compiled]; !ok {
|
||||
f.compiled[compiled] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}}
|
||||
} else {
|
||||
f.compiled[compiled] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, domain := range dynamic.Domains {
|
||||
if f.domains == nil {
|
||||
f.domains = make(map[string]authx.AuthStrategy)
|
||||
f.domains = make(map[string][]authx.AuthStrategy)
|
||||
}
|
||||
f.domains[strings.TrimSpace(domain)] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
|
||||
if strings.HasSuffix(domain, ":80") {
|
||||
f.domains[strings.TrimSuffix(domain, ":80")] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
|
||||
}
|
||||
if strings.HasSuffix(domain, ":443") {
|
||||
f.domains[strings.TrimSuffix(domain, ":443")] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
|
||||
domain = strings.TrimSpace(domain)
|
||||
domain = strings.TrimSuffix(domain, ":80")
|
||||
domain = strings.TrimSuffix(domain, ":443")
|
||||
|
||||
if ss, ok := f.domains[domain]; !ok {
|
||||
f.domains[domain] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}}
|
||||
} else {
|
||||
f.domains[domain] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LookupAddr looks up a given domain/address and returns appropriate auth strategy
|
||||
func (f *FileAuthProvider) LookupAddr(addr string) authx.AuthStrategy {
|
||||
func (f *FileAuthProvider) LookupAddr(addr string) []authx.AuthStrategy {
|
||||
if strings.Contains(addr, ":") {
|
||||
// default normalization for host:port
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
@@ -129,12 +141,12 @@ func (f *FileAuthProvider) LookupAddr(addr string) authx.AuthStrategy {
|
||||
}
|
||||
|
||||
// LookupURL looks up a given URL and returns appropriate auth strategy
|
||||
func (f *FileAuthProvider) LookupURL(u *url.URL) authx.AuthStrategy {
|
||||
func (f *FileAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy {
|
||||
return f.LookupAddr(u.Host)
|
||||
}
|
||||
|
||||
// LookupURLX looks up a given URL and returns appropriate auth strategy
|
||||
func (f *FileAuthProvider) LookupURLX(u *urlutil.URL) authx.AuthStrategy {
|
||||
func (f *FileAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy {
|
||||
return f.LookupAddr(u.Host)
|
||||
}
|
||||
|
||||
@@ -151,17 +163,21 @@ func (f *FileAuthProvider) GetTemplatePaths() []string {
|
||||
|
||||
// PreFetchSecrets pre-fetches the secrets from the auth provider
|
||||
func (f *FileAuthProvider) PreFetchSecrets() error {
|
||||
for _, s := range f.domains {
|
||||
if val, ok := s.(*authx.DynamicAuthStrategy); ok {
|
||||
if err := val.Dynamic.Fetch(false); err != nil {
|
||||
return err
|
||||
for _, ss := range f.domains {
|
||||
for _, s := range ss {
|
||||
if val, ok := s.(*authx.DynamicAuthStrategy); ok {
|
||||
if err := val.Dynamic.Fetch(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range f.compiled {
|
||||
if val, ok := s.(*authx.DynamicAuthStrategy); ok {
|
||||
if err := val.Dynamic.Fetch(false); err != nil {
|
||||
return err
|
||||
for _, ss := range f.compiled {
|
||||
for _, s := range ss {
|
||||
if val, ok := s.(*authx.DynamicAuthStrategy); ok {
|
||||
if err := val.Dynamic.Fetch(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ var (
|
||||
type AuthProvider interface {
|
||||
// LookupAddr looks up a given domain/address and returns appropriate auth strategy
|
||||
// for it (accepted inputs are scanme.sh or scanme.sh:443)
|
||||
LookupAddr(string) authx.AuthStrategy
|
||||
LookupAddr(string) []authx.AuthStrategy
|
||||
// LookupURL looks up a given URL and returns appropriate auth strategy
|
||||
// it accepts a valid url struct and returns the auth strategy
|
||||
LookupURL(*url.URL) authx.AuthStrategy
|
||||
LookupURL(*url.URL) []authx.AuthStrategy
|
||||
// LookupURLX looks up a given URL and returns appropriate auth strategy
|
||||
// it accepts pd url struct (i.e urlutil.URL) and returns the auth strategy
|
||||
LookupURLX(*urlutil.URL) authx.AuthStrategy
|
||||
LookupURLX(*urlutil.URL) []authx.AuthStrategy
|
||||
// GetTemplatePaths returns the template path for the auth provider
|
||||
// that will be used for dynamic secret fetching
|
||||
GetTemplatePaths() []string
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewMultiAuthProvider(providers ...AuthProvider) AuthProvider {
|
||||
return &MultiAuthProvider{Providers: providers}
|
||||
}
|
||||
|
||||
func (m *MultiAuthProvider) LookupAddr(host string) authx.AuthStrategy {
|
||||
func (m *MultiAuthProvider) LookupAddr(host string) []authx.AuthStrategy {
|
||||
for _, provider := range m.Providers {
|
||||
strategy := provider.LookupAddr(host)
|
||||
if strategy != nil {
|
||||
@@ -29,7 +29,7 @@ func (m *MultiAuthProvider) LookupAddr(host string) authx.AuthStrategy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MultiAuthProvider) LookupURL(u *url.URL) authx.AuthStrategy {
|
||||
func (m *MultiAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy {
|
||||
for _, provider := range m.Providers {
|
||||
strategy := provider.LookupURL(u)
|
||||
if strategy != nil {
|
||||
@@ -39,7 +39,7 @@ func (m *MultiAuthProvider) LookupURL(u *url.URL) authx.AuthStrategy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MultiAuthProvider) LookupURLX(u *urlutil.URL) authx.AuthStrategy {
|
||||
func (m *MultiAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy {
|
||||
for _, provider := range m.Providers {
|
||||
strategy := provider.LookupURLX(u)
|
||||
if strategy != nil {
|
||||
|
||||
@@ -31,7 +31,7 @@ const (
|
||||
CLIConfigFileName = "config.yaml"
|
||||
ReportingConfigFilename = "reporting-config.yaml"
|
||||
// Version is the current version of nuclei
|
||||
Version = `v3.3.0`
|
||||
Version = `v3.3.4`
|
||||
// Directory Names of custom templates
|
||||
CustomS3TemplatesDirName = "s3"
|
||||
CustomGitHubTemplatesDirName = "github"
|
||||
|
||||
@@ -216,12 +216,17 @@ func (c *DiskCatalog) findGlobPathMatches(absPath string, processed map[string]s
|
||||
// is a file, it returns true otherwise false with no errors.
|
||||
func (c *DiskCatalog) findFileMatches(absPath string, processed map[string]struct{}) (match string, matched bool, err error) {
|
||||
if c.templatesFS != nil {
|
||||
absPath = strings.TrimPrefix(absPath, "/")
|
||||
absPath = strings.Trim(absPath, "/")
|
||||
}
|
||||
var info fs.File
|
||||
if c.templatesFS == nil {
|
||||
info, err = os.Open(absPath)
|
||||
} else {
|
||||
// If we were given no path, then it's not a file, it's the root, and we can quietly return.
|
||||
if absPath == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
info, err = c.templatesFS.Open(absPath)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -263,6 +268,12 @@ func (c *DiskCatalog) findDirectoryMatches(absPath string, processed map[string]
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// For the special case of the root directory, we need to pass "." to `fs.WalkDir`.
|
||||
if absPath == "" {
|
||||
absPath = "."
|
||||
}
|
||||
absPath = strings.TrimSuffix(absPath, "/")
|
||||
|
||||
err = fs.WalkDir(
|
||||
c.templatesFS,
|
||||
absPath,
|
||||
|
||||
+25
-11
@@ -2,6 +2,7 @@ package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -21,6 +22,11 @@ func (c *DiskCatalog) ResolvePath(templateName, second string) (string, error) {
|
||||
if filepath.IsAbs(templateName) {
|
||||
return templateName, nil
|
||||
}
|
||||
if c.templatesFS != nil {
|
||||
if potentialPath, err := c.tryResolve(templateName); err != errNoValidCombination {
|
||||
return potentialPath, nil
|
||||
}
|
||||
}
|
||||
if second != "" {
|
||||
secondBasePath := filepath.Join(filepath.Dir(second), templateName)
|
||||
if potentialPath, err := c.tryResolve(secondBasePath); err != errNoValidCombination {
|
||||
@@ -28,17 +34,19 @@ func (c *DiskCatalog) ResolvePath(templateName, second string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
curDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
if c.templatesFS == nil {
|
||||
curDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
templatePath := filepath.Join(curDirectory, templateName)
|
||||
if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination {
|
||||
return potentialPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
templatePath := filepath.Join(curDirectory, templateName)
|
||||
if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination {
|
||||
return potentialPath, nil
|
||||
}
|
||||
|
||||
templatePath = filepath.Join(config.DefaultConfig.GetTemplateDir(), templateName)
|
||||
templatePath := filepath.Join(config.DefaultConfig.GetTemplateDir(), templateName)
|
||||
if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination {
|
||||
return potentialPath, nil
|
||||
}
|
||||
@@ -50,8 +58,14 @@ var errNoValidCombination = errors.New("no valid combination found")
|
||||
|
||||
// tryResolve attempts to load locate the target by iterating across all the folders tree
|
||||
func (c *DiskCatalog) tryResolve(fullPath string) (string, error) {
|
||||
if fileutil.FileOrFolderExists(fullPath) {
|
||||
return fullPath, nil
|
||||
if c.templatesFS == nil {
|
||||
if fileutil.FileOrFolderExists(fullPath) {
|
||||
return fullPath, nil
|
||||
}
|
||||
} else {
|
||||
if _, err := fs.Stat(c.templatesFS, fullPath); err == nil {
|
||||
return fullPath, nil
|
||||
}
|
||||
}
|
||||
return "", errNoValidCombination
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader/filter"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/keys"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
const (
|
||||
httpPrefix = "http://"
|
||||
httpsPrefix = "https://"
|
||||
AuthStoreId = "auth_store"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -40,6 +42,7 @@ var (
|
||||
|
||||
// Config contains the configuration options for the loader
|
||||
type Config struct {
|
||||
StoreId string // used to set store id (optional)
|
||||
Templates []string
|
||||
TemplateURLs []string
|
||||
Workflows []string
|
||||
@@ -66,6 +69,7 @@ type Config struct {
|
||||
|
||||
// Store is a storage for loaded nuclei templates
|
||||
type Store struct {
|
||||
id string // id of the store (optional)
|
||||
tagFilter *templates.TagFilter
|
||||
pathFilter *filter.PathFilter
|
||||
config *Config
|
||||
@@ -131,6 +135,7 @@ func New(cfg *Config) (*Store, error) {
|
||||
|
||||
// Create a tag filter based on provided configuration
|
||||
store := &Store{
|
||||
id: cfg.StoreId,
|
||||
config: cfg,
|
||||
tagFilter: tagFilter,
|
||||
pathFilter: filter.NewPathFilter(&filter.PathFilterConfig{
|
||||
@@ -229,6 +234,10 @@ func (store *Store) ReadTemplateFromURI(uri string, remote bool) ([]byte, error)
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) ID() string {
|
||||
return store.id
|
||||
}
|
||||
|
||||
// Templates returns all the templates in the store
|
||||
func (store *Store) Templates() []*templates.Template {
|
||||
return store.templates
|
||||
@@ -434,7 +443,7 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ
|
||||
// increment signed/unsigned counters
|
||||
if tmpl.Verified {
|
||||
if tmpl.TemplateVerifier == "" {
|
||||
templates.SignatureStats[templates.PDVerifier].Add(1)
|
||||
templates.SignatureStats[keys.PDVerifier].Add(1)
|
||||
} else {
|
||||
templates.SignatureStats[tmpl.TemplateVerifier].Add(1)
|
||||
}
|
||||
@@ -471,7 +480,8 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ
|
||||
return
|
||||
}
|
||||
// DAST only templates
|
||||
if store.config.ExecutorOptions.Options.DAST {
|
||||
// Skip DAST filter when loading auth templates
|
||||
if store.ID() != AuthStoreId && store.config.ExecutorOptions.Options.DAST {
|
||||
// check if the template is a DAST template
|
||||
if parsed.IsFuzzing() {
|
||||
loadTemplate(parsed)
|
||||
|
||||
@@ -107,7 +107,7 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
|
||||
currentInfo.Unlock()
|
||||
|
||||
// Skip if the host has had errors
|
||||
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(contextargs.NewWithMetaInput(ctx, scannedValue)) {
|
||||
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, scannedValue)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
|
||||
}
|
||||
if err != nil {
|
||||
if w.Options.HostErrorsCache != nil {
|
||||
w.Options.HostErrorsCache.MarkFailed(ctx.Input, err)
|
||||
w.Options.HostErrorsCache.MarkFailed(w.Options.ProtocolType.String(), ctx.Input, err)
|
||||
}
|
||||
if len(template.Executers) == 1 {
|
||||
mainErr = err
|
||||
@@ -139,7 +139,8 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
|
||||
defer swg.Done()
|
||||
|
||||
// create a new context with the same input but with unset callbacks
|
||||
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
|
||||
// clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running
|
||||
subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
|
||||
if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {
|
||||
gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)
|
||||
}
|
||||
|
||||
+3
-3
@@ -83,8 +83,8 @@ func (bk *customTemplateGitLabRepo) Download(_ context.Context) {
|
||||
|
||||
// Get the directory listing for the files in the project
|
||||
tree, _, err := bk.gitLabClient.Repositories.ListTree(projectID, &gitlab.ListTreeOptions{
|
||||
Ref: gitlab.String(project.DefaultBranch),
|
||||
Recursive: gitlab.Bool(true),
|
||||
Ref: gitlab.Ptr(project.DefaultBranch),
|
||||
Recursive: gitlab.Ptr(true),
|
||||
})
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("error retrieving files from GitLab project: %s (%d) %s", project.Name, projectID, err)
|
||||
@@ -95,7 +95,7 @@ func (bk *customTemplateGitLabRepo) Download(_ context.Context) {
|
||||
// If the object is not a file or file extension is not .yaml, skip it
|
||||
if file.Type == "blob" && filepath.Ext(file.Path) == ".yaml" {
|
||||
gf := &gitlab.GetFileOptions{
|
||||
Ref: gitlab.String(project.DefaultBranch),
|
||||
Ref: gitlab.Ptr(project.DefaultBranch),
|
||||
}
|
||||
f, _, err := bk.gitLabClient.RepositoryFiles.GetFile(projectID, file.Path, gf)
|
||||
if err != nil {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,19 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
|
||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||
)
|
||||
|
||||
type MultiPartForm struct {
|
||||
boundary string
|
||||
boundary string
|
||||
filesMetadata map[string]FileMetadata
|
||||
}
|
||||
|
||||
type FileMetadata struct {
|
||||
ContentType string
|
||||
Filename string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -41,11 +48,40 @@ func (m *MultiPartForm) Encode(data KV) (string, error) {
|
||||
data.Iterate(func(key string, value any) bool {
|
||||
var fw io.Writer
|
||||
var err error
|
||||
|
||||
if filesArray, ok := value.([]interface{}); ok {
|
||||
fileMetadata, ok := m.filesMetadata[key]
|
||||
if !ok {
|
||||
Itererr = fmt.Errorf("file metadata not found for key %s", key)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, file := range filesArray {
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Disposition",
|
||||
fmt.Sprintf(`form-data; name=%q; filename=%q`,
|
||||
key, fileMetadata.Filename))
|
||||
h.Set("Content-Type", fileMetadata.ContentType)
|
||||
|
||||
if fw, err = w.CreatePart(h); err != nil {
|
||||
Itererr = err
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err = fw.Write([]byte(file.(string))); err != nil {
|
||||
Itererr = err
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Add field
|
||||
if fw, err = w.CreateFormField(key); err != nil {
|
||||
Itererr = err
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err = fw.Write([]byte(value.(string))); err != nil {
|
||||
Itererr = err
|
||||
return false
|
||||
@@ -98,6 +134,7 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
|
||||
result.Set(key, values[0])
|
||||
}
|
||||
}
|
||||
m.filesMetadata = make(map[string]FileMetadata)
|
||||
for key, files := range form.File {
|
||||
fileContents := []interface{}{}
|
||||
for _, fileHeader := range files {
|
||||
@@ -112,6 +149,11 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
|
||||
return KV{}, err
|
||||
}
|
||||
fileContents = append(fileContents, buffer.String())
|
||||
|
||||
m.filesMetadata[key] = FileMetadata{
|
||||
ContentType: fileHeader.Header.Get("Content-Type"),
|
||||
Filename: fileHeader.Filename,
|
||||
}
|
||||
}
|
||||
result.Set(key, fileContents)
|
||||
}
|
||||
|
||||
+12
-1
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
errorutil "github.com/projectdiscovery/utils/errors"
|
||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
)
|
||||
@@ -165,6 +166,11 @@ mainLoop:
|
||||
func (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, interactshUrls []string) (map[string]interface{}, []string) {
|
||||
// Check if Interactsh options are configured
|
||||
if rule.options.Interactsh != nil {
|
||||
interactshUrlsMap := make(map[string]struct{})
|
||||
for _, url := range interactshUrls {
|
||||
interactshUrlsMap[url] = struct{}{}
|
||||
}
|
||||
interactshUrls = mapsutil.GetKeys(interactshUrlsMap)
|
||||
// Iterate through the data to replace and evaluate variables with Interactsh URLs
|
||||
for k, v := range data {
|
||||
value := fmt.Sprint(v)
|
||||
@@ -175,7 +181,12 @@ func (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, intera
|
||||
}
|
||||
// Append new OAST URLs if any
|
||||
if len(oastUrls) > 0 {
|
||||
interactshUrls = append(interactshUrls, oastUrls...)
|
||||
for _, url := range oastUrls {
|
||||
if _, ok := interactshUrlsMap[url]; !ok {
|
||||
interactshUrlsMap[url] = struct{}{}
|
||||
interactshUrls = append(interactshUrls, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Evaluate the replaced data
|
||||
evaluatedData, err := expressions.Evaluate(got, data)
|
||||
|
||||
@@ -18,7 +18,7 @@ func getSchemaExample(schema *openapi3.Schema) (interface{}, bool) {
|
||||
return schema.Default, true
|
||||
}
|
||||
|
||||
if schema.Enum != nil && len(schema.Enum) > 0 {
|
||||
if len(schema.Enum) > 0 {
|
||||
return schema.Enum[0], true
|
||||
}
|
||||
return nil, false
|
||||
@@ -175,9 +175,9 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
|
||||
}
|
||||
|
||||
switch {
|
||||
case schema.Type == "boolean":
|
||||
case schema.Type.Is("boolean"):
|
||||
return true, nil
|
||||
case schema.Type == "number", schema.Type == "integer":
|
||||
case schema.Type.Is("number"), schema.Type.Is("integer"):
|
||||
value := 0.0
|
||||
|
||||
if schema.Min != nil && *schema.Min > value {
|
||||
@@ -208,11 +208,11 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
|
||||
value += float64(int(*schema.MultipleOf) - (int(value) % int(*schema.MultipleOf)))
|
||||
}
|
||||
|
||||
if schema.Type == "integer" {
|
||||
if schema.Type.Is("integer") {
|
||||
return int(value), nil
|
||||
}
|
||||
return value, nil
|
||||
case schema.Type == "string":
|
||||
case schema.Type.Is("string"):
|
||||
if ex := stringFormatExample(schema.Format); ex != "" {
|
||||
return ex, nil
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
|
||||
example = example[:*schema.MaxLength]
|
||||
}
|
||||
return example, nil
|
||||
case schema.Type == "array", schema.Items != nil:
|
||||
case schema.Type.Is("array"), schema.Items != nil:
|
||||
example := []interface{}{}
|
||||
|
||||
if schema.Items != nil && schema.Items.Value != nil {
|
||||
@@ -242,7 +242,7 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
|
||||
}
|
||||
}
|
||||
return example, nil
|
||||
case schema.Type == "object", len(schema.Properties) > 0:
|
||||
case schema.Type.Is("object"), len(schema.Properties) > 0:
|
||||
example := map[string]interface{}{}
|
||||
|
||||
for k, v := range schema.Properties {
|
||||
|
||||
@@ -27,7 +27,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
globalAuth = "globalAuth"
|
||||
globalAuth = "globalAuth"
|
||||
DEFAULT_HTTP_SCHEME_HEADER = "Authorization"
|
||||
)
|
||||
|
||||
// GenerateRequestsFromSchema generates http requests from an OpenAPI 3.0 document object
|
||||
@@ -75,16 +76,26 @@ func GenerateRequestsFromSchema(schema *openapi3.T, opts formats.InputFormatOpti
|
||||
|
||||
for _, serverURL := range schema.Servers {
|
||||
pathURL := serverURL.URL
|
||||
// Split the server URL into baseURL and serverPath
|
||||
u, err := url.Parse(pathURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse server url")
|
||||
}
|
||||
baseURL := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||
serverPath := u.Path
|
||||
|
||||
for path, v := range schema.Paths.Map() {
|
||||
// a path item can have parameters
|
||||
ops := v.Operations()
|
||||
requestPath := path
|
||||
if serverPath != "" {
|
||||
requestPath = serverPath + path
|
||||
}
|
||||
for method, ov := range ops {
|
||||
if err := generateRequestsFromOp(&generateReqOptions{
|
||||
requiredOnly: opts.RequiredOnly,
|
||||
method: method,
|
||||
pathURL: pathURL,
|
||||
pathURL: baseURL,
|
||||
requestPath: requestPath,
|
||||
op: ov,
|
||||
schema: schema,
|
||||
@@ -193,7 +204,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error {
|
||||
paramValue = value.Schema.Value.Default
|
||||
} else if value.Schema.Value.Example != nil {
|
||||
paramValue = value.Schema.Value.Example
|
||||
} else if value.Schema.Value.Enum != nil && len(value.Schema.Value.Enum) > 0 {
|
||||
} else if len(value.Schema.Value.Enum) > 0 {
|
||||
paramValue = value.Schema.Value.Enum[0]
|
||||
} else {
|
||||
if !opts.opts.SkipFormatValidation {
|
||||
@@ -406,7 +417,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)
|
||||
@@ -416,18 +427,20 @@ func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*o
|
||||
if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") {
|
||||
return nil, errorutil.NewWithTag("openapi", "unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme)
|
||||
}
|
||||
if scheme.Value.Name == "" {
|
||||
return nil, errorutil.NewWithTag("openapi", "security scheme (%s) name is empty", scheme.Value.Scheme)
|
||||
// HTTP authentication schemes basic or bearer use the Authorization header
|
||||
headerName := scheme.Value.Name
|
||||
if headerName == "" {
|
||||
headerName = DEFAULT_HTTP_SCHEME_HEADER
|
||||
}
|
||||
// create parameters using the scheme
|
||||
switch scheme.Value.Scheme {
|
||||
case "basic":
|
||||
h := openapi3.NewHeaderParameter(scheme.Value.Name)
|
||||
h := openapi3.NewHeaderParameter(headerName)
|
||||
h.Required = true
|
||||
h.Description = globalAuth // differentiator for normal variables and global auth
|
||||
return h, nil
|
||||
case "bearer":
|
||||
h := openapi3.NewHeaderParameter(scheme.Value.Name)
|
||||
h := openapi3.NewHeaderParameter(headerName)
|
||||
h.Required = true
|
||||
h.Description = globalAuth // differentiator for normal variables and global auth
|
||||
return h, nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/invopop/yaml"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2conv"
|
||||
)
|
||||
@@ -49,7 +50,11 @@ func (j *SwaggerFormat) Parse(input string, resultsCb formats.ParseReqRespCallba
|
||||
ext := path.Ext(input)
|
||||
|
||||
if ext == ".yaml" || ext == ".yml" {
|
||||
err = yaml.NewDecoder(file).Decode(schemav2)
|
||||
data, err_data := io.ReadAll(file)
|
||||
if err_data != nil {
|
||||
return errors.Wrap(err, "could not read data file")
|
||||
}
|
||||
err = yaml.Unmarshal(data, schemav2)
|
||||
} else {
|
||||
err = json.NewDecoder(file).Decode(schemav2)
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ func TestSwaggerAPIParser(t *testing.T) {
|
||||
}
|
||||
|
||||
expectedURLs := []string{
|
||||
"https://localhost/users",
|
||||
"https://localhost/users/1?test=asc",
|
||||
"https://localhost/v1/users",
|
||||
"https://localhost/v1/users/1?test=asc",
|
||||
}
|
||||
require.ElementsMatch(t, gotMethodsToURLs, expectedURLs, "could not get swagger urls")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -249,7 +249,7 @@ func identifyGenDecl(pkg *ast.Package, decl *ast.GenDecl, data *TemplateData) {
|
||||
if !spec.Names[0].IsExported() {
|
||||
continue
|
||||
}
|
||||
if spec.Values == nil || len(spec.Values) == 0 {
|
||||
if len(spec.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
data.PackageVars[spec.Names[0].Name] = spec.Names[0].Name
|
||||
|
||||
@@ -489,7 +489,7 @@ func (p *EntityParser) extractVarsNConstants() {
|
||||
if !spec.Names[0].IsExported() {
|
||||
continue
|
||||
}
|
||||
if spec.Values == nil || len(spec.Values) == 0 {
|
||||
if len(spec.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
// get comments or description
|
||||
|
||||
@@ -155,7 +155,7 @@ func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
|
||||
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
|
||||
// client.Authenticate('user', 'password');
|
||||
// ```
|
||||
func (c *Client) Authenticate(username, password string) {
|
||||
func (c *Client) Authenticate(username, password string) bool {
|
||||
c.nj.Require(c.conn != nil, "no existing connection")
|
||||
if c.BaseDN == "" {
|
||||
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
|
||||
@@ -163,19 +163,21 @@ func (c *Client) Authenticate(username, password string) {
|
||||
if err := c.conn.NTLMBind(c.Realm, username, password); err == nil {
|
||||
// if bind with NTLMBind(), there is nothing
|
||||
// else to do, you are authenticated
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
var err error
|
||||
switch password {
|
||||
case "":
|
||||
if err := c.conn.UnauthenticatedBind(username); err != nil {
|
||||
if err = c.conn.UnauthenticatedBind(username); err != nil {
|
||||
c.nj.ThrowError(err)
|
||||
}
|
||||
default:
|
||||
if err := c.conn.Bind(username, password); err != nil {
|
||||
if err = c.conn.Bind(username, password); err != nil {
|
||||
c.nj.ThrowError(err)
|
||||
}
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
|
||||
@@ -185,14 +187,16 @@ func (c *Client) Authenticate(username, password string) {
|
||||
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
|
||||
// client.AuthenticateWithNTLMHash('pdtm', 'hash');
|
||||
// ```
|
||||
func (c *Client) AuthenticateWithNTLMHash(username, hash string) {
|
||||
func (c *Client) AuthenticateWithNTLMHash(username, hash string) bool {
|
||||
c.nj.Require(c.conn != nil, "no existing connection")
|
||||
if c.BaseDN == "" {
|
||||
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
|
||||
}
|
||||
if err := c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
|
||||
var err error
|
||||
if err = c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
|
||||
c.nj.ThrowError(err)
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Search accepts whatever filter and returns a list of maps having provided attributes
|
||||
@@ -279,9 +283,10 @@ func (c *Client) CollectMetadata() Metadata {
|
||||
}
|
||||
metadata.BaseDN = c.BaseDN
|
||||
|
||||
// Use scope as Base since Root DSE doesn't have subentries
|
||||
srMetadata := ldap.NewSearchRequest(
|
||||
"",
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases,
|
||||
0, 0, false,
|
||||
"(objectClass=*)",
|
||||
|
||||
@@ -53,7 +53,7 @@ func BuildDSN(opts MySQLOptions) (string, error) {
|
||||
}
|
||||
target := net.JoinHostPort(opts.Host, fmt.Sprintf("%d", opts.Port))
|
||||
var dsn strings.Builder
|
||||
dsn.WriteString(fmt.Sprintf("%v:%v", url.QueryEscape(opts.Username), url.QueryEscape(opts.Password)))
|
||||
dsn.WriteString(fmt.Sprintf("%v:%v", url.QueryEscape(opts.Username), opts.Password))
|
||||
dsn.WriteString("@")
|
||||
dsn.WriteString(fmt.Sprintf("%v(%v)", opts.Protocol, target))
|
||||
if opts.DbName != "" {
|
||||
|
||||
@@ -60,12 +60,16 @@ func (j *NucleiJS) HandleError(err error, msg ...string) {
|
||||
if len(msg) == 0 {
|
||||
j.ThrowError(err)
|
||||
}
|
||||
j.Throw(fmt.Sprintf("%s: %s", strings.Join(msg, ":"), err.Error()))
|
||||
j.Throw("%s: %s", strings.Join(msg, ":"), err.Error())
|
||||
}
|
||||
|
||||
// Throw throws an error in goja runtime
|
||||
func (j *NucleiJS) Throw(format string, args ...interface{}) {
|
||||
panic(j.runtime().ToValue(fmt.Sprintf(format, args...)))
|
||||
if len(args) > 0 {
|
||||
panic(j.runtime().ToValue(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
panic(j.runtime().ToValue(format))
|
||||
}
|
||||
|
||||
// GetArg returns argument at index from goja runtime if not found throws error
|
||||
@@ -95,7 +99,7 @@ func (j *NucleiJS) GetArgSafe(args []goja.Value, index int, defaultValue any) an
|
||||
// Require throws an error if expression is false
|
||||
func (j *NucleiJS) Require(expr bool, msg string) {
|
||||
if !expr {
|
||||
j.Throw(msg)
|
||||
j.Throw("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,7 @@ package keys
|
||||
|
||||
import _ "embed"
|
||||
|
||||
const PDVerifier = "projectdiscovery/nuclei-templates"
|
||||
|
||||
//go:embed nuclei.crt
|
||||
var NucleiCert []byte // public key for verifying digital signature of templates
|
||||
+65
-5
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -71,6 +72,10 @@ type StandardWriter struct {
|
||||
omitTemplate bool
|
||||
DisableStdout bool
|
||||
AddNewLinesOutputFile bool // by default this is only done for stdout
|
||||
keysToRedact []*regexp.Regexp
|
||||
|
||||
RequestCallback func(templateID, url, requestType string, err error)
|
||||
WriteCallback func(o *ResultEvent)
|
||||
}
|
||||
|
||||
var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
|
||||
@@ -98,6 +103,15 @@ type InternalWrappedEvent struct {
|
||||
InteractshMatched atomic.Bool
|
||||
}
|
||||
|
||||
func (iwe *InternalWrappedEvent) CloneShallow() *InternalWrappedEvent {
|
||||
return &InternalWrappedEvent{
|
||||
InternalEvent: maps.Clone(iwe.InternalEvent),
|
||||
Results: nil,
|
||||
OperatorsResult: nil,
|
||||
UsesInteractsh: iwe.UsesInteractsh,
|
||||
}
|
||||
}
|
||||
|
||||
func (iwe *InternalWrappedEvent) HasOperatorResult() bool {
|
||||
iwe.RLock()
|
||||
defer iwe.RUnlock()
|
||||
@@ -189,6 +203,7 @@ type ResultEvent struct {
|
||||
FuzzingPosition string `json:"fuzzing_position,omitempty"`
|
||||
|
||||
FileToIndexPosition map[string]int `json:"-"`
|
||||
TemplateVerifier string `json:"-"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -254,6 +269,26 @@ func NewStandardWriter(options *types.Options) (*StandardWriter, error) {
|
||||
storeResponseDir: options.StoreResponseDir,
|
||||
omitTemplate: options.OmitTemplate,
|
||||
}
|
||||
for _, key := range options.Redact {
|
||||
keyPattern := regexp.MustCompile(fmt.Sprintf(`(?i)(%s\s*[:=]\s*["']?)[^"'\r\n&]+(["'\r\n]?)`, regexp.QuoteMeta(key)))
|
||||
writer.keysToRedact = append(writer.keysToRedact, keyPattern)
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
func NewStandardWriterWithHooks(
|
||||
options *types.Options,
|
||||
requestCallback func(templateID, url, requestType string, err error),
|
||||
writeCallback func(o *ResultEvent),
|
||||
) (*StandardWriter, error) {
|
||||
writer, err := NewStandardWriter(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writer.RequestCallback = requestCallback
|
||||
writer.WriteCallback = writeCallback
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
@@ -261,7 +296,15 @@ func NewStandardWriter(options *types.Options) (*StandardWriter, error) {
|
||||
func (w *StandardWriter) Write(event *ResultEvent) error {
|
||||
// Enrich the result event with extra metadata on the template-path and url.
|
||||
if event.TemplatePath != "" {
|
||||
event.Template, event.TemplateURL = utils.TemplatePathURL(types.ToString(event.TemplatePath), types.ToString(event.TemplateID))
|
||||
event.Template, event.TemplateURL = utils.TemplatePathURL(types.ToString(event.TemplatePath), types.ToString(event.TemplateID), event.TemplateVerifier)
|
||||
}
|
||||
|
||||
if len(w.keysToRedact) > 0 {
|
||||
event.URL = w.redactKeys(event.URL)
|
||||
event.Request = w.redactKeys(event.Request)
|
||||
event.Response = w.redactKeys(event.Response)
|
||||
event.CURLCommand = w.redactKeys(event.CURLCommand)
|
||||
event.Matched = w.redactKeys(event.Matched)
|
||||
}
|
||||
|
||||
event.Timestamp = time.Now()
|
||||
@@ -299,9 +342,20 @@ func (w *StandardWriter) Write(event *ResultEvent) error {
|
||||
_, _ = w.outputFile.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
if w.WriteCallback != nil {
|
||||
w.WriteCallback(event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *StandardWriter) redactKeys(data string) string {
|
||||
for _, keyPattern := range w.keysToRedact {
|
||||
data = keyPattern.ReplaceAllString(data, `$1***$2`)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// JSONLogRequest is a trace/error log request written to file
|
||||
type JSONLogRequest struct {
|
||||
Template string `json:"template"`
|
||||
@@ -316,7 +370,11 @@ type JSONLogRequest struct {
|
||||
|
||||
// Request writes a log the requests trace log
|
||||
func (w *StandardWriter) Request(templatePath, input, requestType string, requestErr error) {
|
||||
if w.traceFile == nil && w.errorFile == nil {
|
||||
if w.traceFile == nil && w.errorFile == nil && w.RequestCallback == nil {
|
||||
return
|
||||
}
|
||||
if w.RequestCallback != nil {
|
||||
w.RequestCallback(templatePath, input, requestType, requestErr)
|
||||
return
|
||||
}
|
||||
request := &JSONLogRequest{
|
||||
@@ -365,6 +423,7 @@ func (w *StandardWriter) Request(templatePath, input, requestType string, reques
|
||||
if val := errkit.GetAttrValue(requestErr, "address"); val.Any() != nil {
|
||||
request.Address = val.String()
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(request)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -377,6 +436,7 @@ func (w *StandardWriter) Request(templatePath, input, requestType string, reques
|
||||
if requestErr != nil && w.errorFile != nil {
|
||||
_, _ = w.errorFile.Write(data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Colorizer returns the colorizer instance for writer
|
||||
@@ -418,7 +478,7 @@ func (w *StandardWriter) WriteFailure(wrappedEvent *InternalWrappedEvent) error
|
||||
// if no results were found, manually create a failure event
|
||||
event := wrappedEvent.InternalEvent
|
||||
|
||||
templatePath, templateURL := utils.TemplatePathURL(types.ToString(event["template-path"]), types.ToString(event["template-id"]))
|
||||
templatePath, templateURL := utils.TemplatePathURL(types.ToString(event["template-path"]), types.ToString(event["template-id"]), types.ToString(event["template-verifier"]))
|
||||
var templateInfo model.Info
|
||||
if event["template-info"] != nil {
|
||||
templateInfo = event["template-info"].(model.Info)
|
||||
@@ -508,12 +568,12 @@ func tryParseCause(err error) error {
|
||||
if strings.HasPrefix(msg, "ReadStatusLine:") {
|
||||
// last index is actual error (from rawhttp)
|
||||
parts := strings.Split(msg, ":")
|
||||
return errkit.New(strings.TrimSpace(parts[len(parts)-1]))
|
||||
return errkit.New("%s", strings.TrimSpace(parts[len(parts)-1]))
|
||||
}
|
||||
if strings.Contains(msg, "read ") {
|
||||
// same here
|
||||
parts := strings.Split(msg, ":")
|
||||
return errkit.New(strings.TrimSpace(parts[len(parts)-1]))
|
||||
return errkit.New("%s", strings.TrimSpace(parts[len(parts)-1]))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func hash(v interface{}) (string, error) {
|
||||
|
||||
sh := sha256.New()
|
||||
|
||||
if _, err = io.WriteString(sh, string(data)); err != nil {
|
||||
if _, err = sh.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(sh.Sum(nil)), nil
|
||||
|
||||
@@ -348,6 +348,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(request.options.TemplateID),
|
||||
TemplatePath: types.ToString(request.options.TemplatePath),
|
||||
Info: request.options.TemplateInfo,
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["input"]),
|
||||
Host: fields.Host,
|
||||
|
||||
@@ -22,6 +22,13 @@ func (g *PayloadGenerator) validate(payloads map[string]interface{}, templatePat
|
||||
return errors.New("invalid number of lines in payload")
|
||||
}
|
||||
|
||||
// For historical reasons, "validate" checks to see if the payload file exist.
|
||||
// If we're using a custom helper function, then we need to skip any validation beyond just checking the string syntax.
|
||||
// Actually attempting to load the file will determine whether or not it exists.
|
||||
if g.options.LoadHelperFileFunction != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if it's a file and try to load it
|
||||
if fileutil.FileExists(payloadType) {
|
||||
continue
|
||||
|
||||
@@ -44,7 +44,8 @@ func toHighLightedHexDump(hexDump, snippetToHighlight string) (HighlightableHexD
|
||||
hexDumpRowValues := hexDumpParsePattern.FindAllStringSubmatch(hexDump, -1)
|
||||
if hexDumpRowValues == nil || len(hexDumpRowValues) != strings.Count(hexDump, "\n") {
|
||||
message := "could not parse hexdump"
|
||||
gologger.Warning().Msgf(message)
|
||||
gologger.Warning().Msg(message)
|
||||
|
||||
return HighlightableHexDump{}, errors.New(message)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
// CacheInterface defines the signature of the hosterrorscache so that
|
||||
// users of Nuclei as embedded lib may implement their own cache
|
||||
type CacheInterface interface {
|
||||
SetVerbose(verbose bool) // log verbosely
|
||||
Close() // close the cache
|
||||
Check(ctx *contextargs.Context) bool // return true if the host should be skipped
|
||||
MarkFailed(ctx *contextargs.Context, err error) // record a failure (and cause) for the host
|
||||
SetVerbose(verbose bool) // log verbosely
|
||||
Close() // close the cache
|
||||
Check(protoType string, ctx *contextargs.Context) bool // return true if the host should be skipped
|
||||
MarkFailed(protoType string, ctx *contextargs.Context, err error) // record a failure (and cause) for the host
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -75,24 +75,34 @@ func (c *Cache) Close() {
|
||||
c.failedTargets.Purge()
|
||||
}
|
||||
|
||||
func (c *Cache) normalizeCacheValue(value string) string {
|
||||
finalValue := value
|
||||
if strings.HasPrefix(value, "http") {
|
||||
if parsed, err := url.Parse(value); err == nil {
|
||||
hostname := parsed.Host
|
||||
finalPort := parsed.Port()
|
||||
if finalPort == "" {
|
||||
if parsed.Scheme == "https" {
|
||||
finalPort = "443"
|
||||
} else {
|
||||
finalPort = "80"
|
||||
}
|
||||
hostname = net.JoinHostPort(parsed.Host, finalPort)
|
||||
// NormalizeCacheValue processes the input value and returns a normalized cache
|
||||
// value.
|
||||
func (c *Cache) NormalizeCacheValue(value string) string {
|
||||
var normalizedValue string = value
|
||||
|
||||
u, err := url.ParseRequestURI(value)
|
||||
if err != nil || u.Host == "" {
|
||||
u, err2 := url.ParseRequestURI("https://" + value)
|
||||
if err2 != nil {
|
||||
return normalizedValue
|
||||
}
|
||||
|
||||
normalizedValue = u.Host
|
||||
} else {
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
normalizedValue = net.JoinHostPort(u.Host, "443")
|
||||
case "http":
|
||||
normalizedValue = net.JoinHostPort(u.Host, "80")
|
||||
}
|
||||
finalValue = hostname
|
||||
} else {
|
||||
normalizedValue = u.Host
|
||||
}
|
||||
}
|
||||
return finalValue
|
||||
|
||||
return normalizedValue
|
||||
}
|
||||
|
||||
// ErrUnresponsiveHost is returned when a host is unresponsive
|
||||
@@ -105,7 +115,7 @@ func (c *Cache) normalizeCacheValue(value string) string {
|
||||
// - URL: https?:// type
|
||||
// - Host:port type
|
||||
// - host type
|
||||
func (c *Cache) Check(ctx *contextargs.Context) bool {
|
||||
func (c *Cache) Check(protoType string, ctx *contextargs.Context) bool {
|
||||
finalValue := c.GetKeyFromContext(ctx, nil)
|
||||
|
||||
existingCacheItem, err := c.failedTargets.GetIFPresent(finalValue)
|
||||
@@ -128,8 +138,8 @@ func (c *Cache) Check(ctx *contextargs.Context) bool {
|
||||
}
|
||||
|
||||
// MarkFailed marks a host as failed previously
|
||||
func (c *Cache) MarkFailed(ctx *contextargs.Context, err error) {
|
||||
if !c.checkError(err) {
|
||||
func (c *Cache) MarkFailed(protoType string, ctx *contextargs.Context, err error) {
|
||||
if !c.checkError(protoType, err) {
|
||||
return
|
||||
}
|
||||
finalValue := c.GetKeyFromContext(ctx, err)
|
||||
@@ -166,7 +176,7 @@ func (c *Cache) GetKeyFromContext(ctx *contextargs.Context, err error) string {
|
||||
address = tmp.String()
|
||||
}
|
||||
}
|
||||
finalValue := c.normalizeCacheValue(address)
|
||||
finalValue := c.NormalizeCacheValue(address)
|
||||
return finalValue
|
||||
}
|
||||
|
||||
@@ -176,11 +186,13 @@ var reCheckError = regexp.MustCompile(`(no address found for host|could not reso
|
||||
// added to the host skipping table.
|
||||
// it first parses error and extracts the cause and checks for blacklisted
|
||||
// or common errors that should be skipped
|
||||
func (c *Cache) checkError(err error) bool {
|
||||
func (c *Cache) checkError(protoType string, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if protoType != "http" {
|
||||
return false
|
||||
}
|
||||
kind := errkit.GetErrorKind(err, nucleierr.ErrTemplateLogic)
|
||||
switch kind {
|
||||
case nucleierr.ErrTemplateLogic:
|
||||
|
||||
@@ -11,12 +11,16 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
protoType = "http"
|
||||
)
|
||||
|
||||
func TestCacheCheck(t *testing.T) {
|
||||
cache := New(3, DefaultMaxHostsCount, nil)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
cache.MarkFailed(newCtxArgs("test"), fmt.Errorf("could not resolve host"))
|
||||
got := cache.Check(newCtxArgs("test"))
|
||||
cache.MarkFailed(protoType, newCtxArgs("test"), fmt.Errorf("could not resolve host"))
|
||||
got := cache.Check(protoType, newCtxArgs("test"))
|
||||
if i < 2 {
|
||||
// till 3 the host is not flagged to skip
|
||||
require.False(t, got)
|
||||
@@ -26,7 +30,7 @@ func TestCacheCheck(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
value := cache.Check(newCtxArgs("test"))
|
||||
value := cache.Check(protoType, newCtxArgs("test"))
|
||||
require.Equal(t, true, value, "could not get checked value")
|
||||
}
|
||||
|
||||
@@ -34,8 +38,8 @@ func TestTrackErrors(t *testing.T) {
|
||||
cache := New(3, DefaultMaxHostsCount, []string{"custom error"})
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
cache.MarkFailed(newCtxArgs("custom"), fmt.Errorf("got: nested: custom error"))
|
||||
got := cache.Check(newCtxArgs("custom"))
|
||||
cache.MarkFailed(protoType, newCtxArgs("custom"), fmt.Errorf("got: nested: custom error"))
|
||||
got := cache.Check(protoType, newCtxArgs("custom"))
|
||||
if i < 2 {
|
||||
// till 3 the host is not flagged to skip
|
||||
require.False(t, got)
|
||||
@@ -44,7 +48,7 @@ func TestTrackErrors(t *testing.T) {
|
||||
require.True(t, got)
|
||||
}
|
||||
}
|
||||
value := cache.Check(newCtxArgs("custom"))
|
||||
value := cache.Check(protoType, newCtxArgs("custom"))
|
||||
require.Equal(t, true, value, "could not get checked value")
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ func TestCacheMarkFailed(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
normalizedCacheValue := cache.GetKeyFromContext(newCtxArgs(test.host), nil)
|
||||
cache.MarkFailed(newCtxArgs(test.host), fmt.Errorf("no address found for host"))
|
||||
cache.MarkFailed(protoType, newCtxArgs(test.host), fmt.Errorf("no address found for host"))
|
||||
failedTarget, err := cache.failedTargets.Get(normalizedCacheValue)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, failedTarget)
|
||||
@@ -109,7 +113,7 @@ func TestCacheMarkFailedConcurrent(t *testing.T) {
|
||||
|
||||
// the cache is not atomic during items creation, so we pre-create them with counter to zero
|
||||
for _, test := range tests {
|
||||
normalizedValue := cache.normalizeCacheValue(test.host)
|
||||
normalizedValue := cache.NormalizeCacheValue(test.host)
|
||||
newItem := &cacheItem{errors: atomic.Int32{}}
|
||||
newItem.errors.Store(0)
|
||||
_ = cache.failedTargets.Set(normalizedValue, newItem)
|
||||
@@ -122,16 +126,16 @@ func TestCacheMarkFailedConcurrent(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
cache.MarkFailed(newCtxArgs(currentTest.host), fmt.Errorf("could not resolve host"))
|
||||
cache.MarkFailed(protoType, newCtxArgs(currentTest.host), fmt.Errorf("could not resolve host"))
|
||||
}()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, test := range tests {
|
||||
require.True(t, cache.Check(newCtxArgs(test.host)))
|
||||
require.True(t, cache.Check(protoType, newCtxArgs(test.host)))
|
||||
|
||||
normalizedCacheValue := cache.normalizeCacheValue(test.host)
|
||||
normalizedCacheValue := cache.NormalizeCacheValue(test.host)
|
||||
failedTarget, err := cache.failedTargets.Get(normalizedCacheValue)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, failedTarget)
|
||||
|
||||
@@ -77,9 +77,8 @@ var muGlobalChange sync.Mutex
|
||||
|
||||
// Global setting
|
||||
func GlobalGuardBytesBufferAlloc() error {
|
||||
if muGlobalChange.TryLock() {
|
||||
if !muGlobalChange.TryLock() {
|
||||
return nil
|
||||
|
||||
}
|
||||
defer muGlobalChange.Unlock()
|
||||
|
||||
@@ -95,9 +94,8 @@ func GlobalGuardBytesBufferAlloc() error {
|
||||
|
||||
// Global setting
|
||||
func GlobalRestoreBytesBufferAlloc() {
|
||||
if muGlobalChange.TryLock() {
|
||||
if !muGlobalChange.TryLock() {
|
||||
return
|
||||
|
||||
}
|
||||
defer muGlobalChange.Unlock()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/pkg/errors"
|
||||
@@ -19,9 +20,17 @@ import (
|
||||
|
||||
// Dialer is a shared fastdialer instance for host DNS resolution
|
||||
var (
|
||||
Dialer *fastdialer.Dialer
|
||||
muDialer sync.RWMutex
|
||||
Dialer *fastdialer.Dialer
|
||||
)
|
||||
|
||||
func GetDialer() *fastdialer.Dialer {
|
||||
muDialer.RLock()
|
||||
defer muDialer.RUnlock()
|
||||
|
||||
return Dialer
|
||||
}
|
||||
|
||||
func ShouldInit() bool {
|
||||
return Dialer == nil
|
||||
}
|
||||
@@ -34,6 +43,10 @@ func Init(options *types.Options) error {
|
||||
|
||||
lfaAllowed = options.AllowLocalFileAccess
|
||||
opts := fastdialer.DefaultOptions
|
||||
if options.DisableDefaultResolvers {
|
||||
opts.HostsFile = false
|
||||
opts.ResolversFile = false
|
||||
}
|
||||
opts.DialerTimeout = options.GetTimeouts().DialTimeout
|
||||
if options.DialerKeepAlive > 0 {
|
||||
opts.DialerKeepAlive = options.DialerKeepAlive
|
||||
@@ -125,6 +138,7 @@ func Init(options *types.Options) error {
|
||||
if options.SystemResolvers {
|
||||
opts.ResolversFile = true
|
||||
opts.EnableFallback = true
|
||||
opts.HostsFile = true
|
||||
}
|
||||
if len(options.InternalResolversList) > 0 {
|
||||
opts.BaseResolvers = options.InternalResolversList
|
||||
@@ -210,10 +224,12 @@ func interfaceAddresses(interfaceName string) ([]net.Addr, error) {
|
||||
|
||||
// Close closes the global shared fastdialer
|
||||
func Close() {
|
||||
muDialer.Lock()
|
||||
defer muDialer.Unlock()
|
||||
|
||||
if Dialer != nil {
|
||||
Dialer.Close()
|
||||
Dialer = nil
|
||||
}
|
||||
Dialer = nil
|
||||
StopActiveMemGuardian()
|
||||
}
|
||||
@@ -114,6 +114,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: types.ToString(wrapped.InternalEvent["host"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["matched"]),
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (request *Request) execute(input *contextargs.Context, domain string, metad
|
||||
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
|
||||
msg := fmt.Sprintf("[%s] Dumped DNS request for %s", request.options.TemplateID, question)
|
||||
if request.options.Options.Debug || request.options.Options.DebugRequests {
|
||||
gologger.Info().Str("domain", domain).Msgf(msg)
|
||||
gologger.Info().Str("domain", domain).Msg(msg)
|
||||
gologger.Print().Msgf("%s", requestString)
|
||||
}
|
||||
if request.options.Options.StoreResponse {
|
||||
@@ -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) {
|
||||
|
||||
@@ -104,6 +104,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Path: types.ToString(wrapped.InternalEvent["path"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["matched"]),
|
||||
|
||||
@@ -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 (
|
||||
@@ -41,9 +45,24 @@ const (
|
||||
// ActionFilesInput performs an action on a file input.
|
||||
// name:files
|
||||
ActionFilesInput
|
||||
// ActionWaitLoad waits for the page to stop loading.
|
||||
// ActionWaitDOM waits for the HTML document has been completely loaded & parsed.
|
||||
// name:waitdom
|
||||
ActionWaitDOM
|
||||
// ActionWaitFCP waits for the first piece of content (text, image, etc.) is painted on the screen.
|
||||
// name:waitfcp
|
||||
ActionWaitFCP
|
||||
// ActionWaitFMP waits for page has rendered enough meaningful content to be useful to the user.
|
||||
// name:waitfmp
|
||||
ActionWaitFMP
|
||||
// ActionWaitIdle waits for the network is completely idle (no ongoing network requests).
|
||||
// name:waitidle
|
||||
ActionWaitIdle
|
||||
// ActionWaitLoad waits for the page and all its resources (like stylesheets and images) have finished loading.
|
||||
// name:waitload
|
||||
ActionWaitLoad
|
||||
// ActionWaitStable waits until the page is stable.
|
||||
// name:waitstable
|
||||
ActionWaitStable
|
||||
// ActionGetResource performs a get resource action on an element
|
||||
// name:getresource
|
||||
ActionGetResource
|
||||
@@ -68,6 +87,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
|
||||
@@ -95,7 +117,12 @@ var ActionStringToAction = map[string]ActionType{
|
||||
"time": ActionTimeInput,
|
||||
"select": ActionSelectInput,
|
||||
"files": ActionFilesInput,
|
||||
"waitdom": ActionWaitDOM,
|
||||
"waitfcp": ActionWaitFCP,
|
||||
"waitfmp": ActionWaitFMP,
|
||||
"waitidle": ActionWaitIdle,
|
||||
"waitload": ActionWaitLoad,
|
||||
"waitstable": ActionWaitStable,
|
||||
"getresource": ActionGetResource,
|
||||
"extract": ActionExtract,
|
||||
"setmethod": ActionSetMethod,
|
||||
@@ -104,6 +131,7 @@ var ActionStringToAction = map[string]ActionType{
|
||||
"deleteheader": ActionDeleteHeader,
|
||||
"setbody": ActionSetBody,
|
||||
"waitevent": ActionWaitEvent,
|
||||
"waitdialog": ActionWaitDialog,
|
||||
"keyboard": ActionKeyboard,
|
||||
"debug": ActionDebug,
|
||||
"sleep": ActionSleep,
|
||||
@@ -121,7 +149,12 @@ var ActionToActionString = map[ActionType]string{
|
||||
ActionTimeInput: "time",
|
||||
ActionSelectInput: "select",
|
||||
ActionFilesInput: "files",
|
||||
ActionWaitDOM: "waitdom",
|
||||
ActionWaitFCP: "waitfcp",
|
||||
ActionWaitFMP: "waitfmp",
|
||||
ActionWaitIdle: "waitidle",
|
||||
ActionWaitLoad: "waitload",
|
||||
ActionWaitStable: "waitstable",
|
||||
ActionGetResource: "getresource",
|
||||
ActionExtract: "extract",
|
||||
ActionSetMethod: "setmethod",
|
||||
@@ -130,6 +163,7 @@ var ActionToActionString = map[ActionType]string{
|
||||
ActionDeleteHeader: "deleteheader",
|
||||
ActionSetBody: "setbody",
|
||||
ActionWaitEvent: "waitevent",
|
||||
ActionWaitDialog: "waitdialog",
|
||||
ActionKeyboard: "keyboard",
|
||||
ActionDebug: "debug",
|
||||
ActionSleep: "sleep",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -88,8 +94,28 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, var
|
||||
err = p.TimeInputElement(act, outData)
|
||||
case ActionSelectInput:
|
||||
err = p.SelectInputElement(act, outData)
|
||||
case ActionWaitDOM:
|
||||
event := proto.PageLifecycleEventNameDOMContentLoaded
|
||||
err = p.WaitPageLifecycleEvent(act, outData, event)
|
||||
case ActionWaitFCP:
|
||||
event := proto.PageLifecycleEventNameFirstContentfulPaint
|
||||
err = p.WaitPageLifecycleEvent(act, outData, event)
|
||||
case ActionWaitFMP:
|
||||
event := proto.PageLifecycleEventNameFirstMeaningfulPaint
|
||||
err = p.WaitPageLifecycleEvent(act, outData, event)
|
||||
case ActionWaitIdle:
|
||||
event := proto.PageLifecycleEventNameNetworkIdle
|
||||
err = p.WaitPageLifecycleEvent(act, outData, event)
|
||||
case ActionWaitLoad:
|
||||
err = p.WaitLoad(act, outData)
|
||||
event := proto.PageLifecycleEventNameLoad
|
||||
err = p.WaitPageLifecycleEvent(act, outData, event)
|
||||
case ActionWaitStable:
|
||||
err = p.WaitStable(act, outData)
|
||||
// NOTE(dwisiswant0): Mapping `ActionWaitLoad` to `Page.WaitStable`,
|
||||
// just in case waiting for the `proto.PageLifecycleEventNameLoad` event
|
||||
// doesn't meet expectations.
|
||||
// case ActionWaitLoad, ActionWaitStable:
|
||||
// err = p.WaitStable(act, outData)
|
||||
case ActionGetResource:
|
||||
err = p.GetResource(act, outData)
|
||||
case ActionExtract:
|
||||
@@ -100,6 +126,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 +170,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")
|
||||
@@ -196,6 +224,17 @@ func createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {
|
||||
}
|
||||
}
|
||||
|
||||
func getNavigationFunc(p *Page, act *Action, event proto.PageLifecycleEventName) (func(), error) {
|
||||
dur, err := getTimeout(p, act)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Wrong timeout given")
|
||||
}
|
||||
|
||||
fn := p.page.Timeout(dur).WaitNavigation(event)
|
||||
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func getTimeout(p *Page, act *Action) (time.Duration, error) {
|
||||
return geTimeParameter(p, act, "timeout", 3, time.Second)
|
||||
}
|
||||
@@ -217,7 +256,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 +267,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 +278,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 +288,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 +298,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 +308,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 +364,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 +385,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 +400,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 +420,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 +483,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 +502,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 +525,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
|
||||
@@ -510,22 +549,40 @@ func (p *Page) SelectInputElement(act *Action, out map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitLoad waits for the page to load
|
||||
func (p *Page) WaitLoad(act *Action, out map[string]string) error {
|
||||
p.page.Timeout(2 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
|
||||
|
||||
// Wait for the window.onload event and also wait for the network requests
|
||||
// to become idle for a maximum duration of 3 seconds. If the requests
|
||||
// do not finish,
|
||||
if err := p.page.WaitLoad(); err != nil {
|
||||
return errors.Wrap(err, "could not wait load event")
|
||||
// WaitPageLifecycleEvent waits for specified page lifecycle event name
|
||||
func (p *Page) WaitPageLifecycleEvent(act *Action, out ActionData, event proto.PageLifecycleEventName) error {
|
||||
fn, err := getNavigationFunc(p, act, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = p.page.WaitIdle(1 * time.Second)
|
||||
|
||||
fn()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitStable waits until the page is stable
|
||||
func (p *Page) WaitStable(act *Action, out ActionData) error {
|
||||
var dur time.Duration = time.Second // default stable page duration: 1s
|
||||
|
||||
timeout, err := getTimeout(p, act)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Wrong timeout given")
|
||||
}
|
||||
|
||||
argDur := act.Data["duration"]
|
||||
if argDur != "" {
|
||||
dur, err = time.ParseDuration(argDur)
|
||||
if err != nil {
|
||||
dur = time.Second
|
||||
}
|
||||
}
|
||||
|
||||
return p.page.Timeout(timeout).WaitStable(dur)
|
||||
}
|
||||
|
||||
// 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 +598,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 +615,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 +649,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 +687,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 +758,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")
|
||||
})
|
||||
@@ -201,11 +201,11 @@ func TestActionScreenshot(t *testing.T) {
|
||||
filePath := filepath.Join(os.TempDir(), "test.png")
|
||||
actions := []*Action{
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},
|
||||
{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()
|
||||
@@ -229,11 +229,11 @@ func TestActionScreenshotToDir(t *testing.T) {
|
||||
|
||||
actions := []*Action{
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
|
||||
{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},
|
||||
{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)
|
||||
|
||||
@@ -137,6 +137,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Path: fields.Path,
|
||||
|
||||
@@ -174,7 +174,7 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p
|
||||
reqBuilder.WriteString("\t" + actStepStr + "\n")
|
||||
}
|
||||
}
|
||||
gologger.Debug().Msgf(reqBuilder.String())
|
||||
gologger.Debug().Msg(reqBuilder.String())
|
||||
}
|
||||
|
||||
var responseBody string
|
||||
@@ -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) {
|
||||
|
||||
@@ -90,9 +90,9 @@ func (g *generatedRequest) ApplyAuth(provider authprovider.AuthProvider) {
|
||||
return
|
||||
}
|
||||
if g.request != nil {
|
||||
auth := provider.LookupURLX(g.request.URL)
|
||||
if auth != nil {
|
||||
auth.ApplyOnRR(g.request)
|
||||
authStrategies := provider.LookupURLX(g.request.URL)
|
||||
for _, strategy := range authStrategies {
|
||||
strategy.ApplyOnRR(g.request)
|
||||
}
|
||||
}
|
||||
if g.rawRequest != nil {
|
||||
@@ -101,11 +101,11 @@ func (g *generatedRequest) ApplyAuth(provider authprovider.AuthProvider) {
|
||||
gologger.Warning().Msgf("[authprovider] Could not parse URL %s: %s\n", g.rawRequest.FullURL, err)
|
||||
return
|
||||
}
|
||||
auth := provider.LookupURLX(parsed)
|
||||
if auth != nil {
|
||||
// here we need to apply it custom because we don't have a standard/official
|
||||
// rawhttp request format ( which we probably should have )
|
||||
g.rawRequest.ApplyAuthStrategy(auth)
|
||||
authStrategies := provider.LookupURLX(parsed)
|
||||
// here we need to apply it custom because we don't have a standard/official
|
||||
// rawhttp request format ( which we probably should have )
|
||||
for _, strategy := range authStrategies {
|
||||
g.rawRequest.ApplyAuthStrategy(strategy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ type Request struct {
|
||||
// - "AWS"
|
||||
Signature SignatureTypeHolder `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS"`
|
||||
|
||||
// description: |
|
||||
// SkipSecretFile skips the authentication or authorization configured in the secret file.
|
||||
SkipSecretFile bool `yaml:"skip-secret-file,omitempty" json:"skip-secret-file,omitempty" jsonschema:"title=bypass secret file,description=Skips the authentication or authorization configured in the secret file"`
|
||||
|
||||
// description: |
|
||||
// CookieReuse is an optional setting that enables cookie reuse for
|
||||
// all requests defined in raw section.
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
var (
|
||||
rawHttpClient *rawhttp.Client
|
||||
rawHttpClientOnce sync.Once
|
||||
forceMaxRedirects int
|
||||
normalClient *retryablehttp.Client
|
||||
clientPool *mapsutil.SyncLockMap[string, *retryablehttp.Client]
|
||||
@@ -102,6 +103,22 @@ type Configuration struct {
|
||||
ResponseHeaderTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *Configuration) Clone() *Configuration {
|
||||
clone := *c
|
||||
if c.Connection != nil {
|
||||
cloneConnection := &ConnectionConfiguration{
|
||||
DisableKeepAlive: c.Connection.DisableKeepAlive,
|
||||
}
|
||||
if c.Connection.HasCookieJar() {
|
||||
cookiejar := *c.Connection.GetCookieJar()
|
||||
cloneConnection.SetCookieJar(&cookiejar)
|
||||
}
|
||||
clone.Connection = cloneConnection
|
||||
}
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
// Hash returns the hash of the configuration to allow client pooling
|
||||
func (c *Configuration) Hash() string {
|
||||
builder := &strings.Builder{}
|
||||
@@ -131,7 +148,7 @@ func (c *Configuration) HasStandardOptions() bool {
|
||||
|
||||
// GetRawHTTP returns the rawhttp request client
|
||||
func GetRawHTTP(options *protocols.ExecutorOptions) *rawhttp.Client {
|
||||
if rawHttpClient == nil {
|
||||
rawHttpClientOnce.Do(func() {
|
||||
rawHttpOptions := rawhttp.DefaultOptions
|
||||
if types.ProxyURL != "" {
|
||||
rawHttpOptions.Proxy = types.ProxyURL
|
||||
@@ -142,7 +159,7 @@ func GetRawHTTP(options *protocols.ExecutorOptions) *rawhttp.Client {
|
||||
}
|
||||
rawHttpOptions.Timeout = options.Options.GetTimeouts().HttpTimeout
|
||||
rawHttpClient = rawhttp.NewClient(rawHttpOptions)
|
||||
}
|
||||
})
|
||||
return rawHttpClient
|
||||
}
|
||||
|
||||
@@ -233,7 +250,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl
|
||||
|
||||
transport := &http.Transport{
|
||||
ForceAttemptHTTP2: options.ForceAttemptHTTP2,
|
||||
DialContext: protocolstate.Dialer.Dial,
|
||||
DialContext: protocolstate.GetDialer().Dial,
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if options.TlsImpersonate {
|
||||
return protocolstate.Dialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil)
|
||||
@@ -241,7 +258,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl
|
||||
if options.HasClientCertificates() || options.ForceAttemptHTTP2 {
|
||||
return protocolstate.Dialer.DialTLSWithConfig(ctx, network, addr, tlsConfig)
|
||||
}
|
||||
return protocolstate.Dialer.DialTLS(ctx, network, addr)
|
||||
return protocolstate.GetDialer().DialTLS(ctx, network, addr)
|
||||
},
|
||||
MaxIdleConns: maxIdleConns,
|
||||
MaxIdleConnsPerHost: maxIdleConnsPerHost,
|
||||
|
||||
@@ -170,6 +170,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
|
||||
@@ -689,7 +689,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
||||
}
|
||||
|
||||
// === apply auth strategies ===
|
||||
if generatedRequest.request != nil {
|
||||
if generatedRequest.request != nil && !request.SkipSecretFile {
|
||||
generatedRequest.ApplyAuth(request.options.AuthProvider)
|
||||
}
|
||||
|
||||
@@ -770,7 +770,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
||||
|
||||
// check for cookie related configuration
|
||||
if input.CookieJar != nil {
|
||||
connConfiguration := request.connConfiguration
|
||||
connConfiguration := request.connConfiguration.Clone()
|
||||
connConfiguration.Connection.SetCookieJar(input.CookieJar)
|
||||
modifiedConfig = connConfiguration
|
||||
}
|
||||
@@ -778,7 +778,8 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
||||
updatedTimeout, ok := generatedRequest.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)
|
||||
if ok {
|
||||
if modifiedConfig == nil {
|
||||
modifiedConfig = request.connConfiguration
|
||||
connConfiguration := request.connConfiguration.Clone()
|
||||
modifiedConfig = connConfiguration
|
||||
}
|
||||
modifiedConfig.ResponseHeaderTimeout = updatedTimeout.Timeout
|
||||
}
|
||||
@@ -941,7 +942,11 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
||||
if input.MetaInput.CustomIP != "" {
|
||||
outputEvent["ip"] = input.MetaInput.CustomIP
|
||||
} else {
|
||||
outputEvent["ip"] = protocolstate.Dialer.GetDialedIP(hostname)
|
||||
dialer := protocolstate.GetDialer()
|
||||
if dialer != nil {
|
||||
outputEvent["ip"] = dialer.GetDialedIP(hostname)
|
||||
}
|
||||
|
||||
// try getting cname
|
||||
request.addCNameIfAvailable(hostname, outputEvent)
|
||||
}
|
||||
@@ -1037,6 +1042,10 @@ func (request *Request) validateNFixEvent(input *contextargs.Context, gr *genera
|
||||
|
||||
// addCNameIfAvailable adds the cname to the event if available
|
||||
func (request *Request) addCNameIfAvailable(hostname string, outputEvent map[string]interface{}) {
|
||||
if protocolstate.Dialer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := protocolstate.Dialer.GetDNSData(hostname)
|
||||
if err == nil {
|
||||
switch len(data.CNAME) {
|
||||
@@ -1168,14 +1177,14 @@ func (request *Request) markUnresponsiveAddress(input *contextargs.Context, err
|
||||
return
|
||||
}
|
||||
if request.options.HostErrorsCache != nil {
|
||||
request.options.HostErrorsCache.MarkFailed(input, err)
|
||||
request.options.HostErrorsCache.MarkFailed(request.options.ProtocolType.String(), input, err)
|
||||
}
|
||||
}
|
||||
|
||||
// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history
|
||||
func (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {
|
||||
if request.options.HostErrorsCache != nil {
|
||||
return request.options.HostErrorsCache.Check(input)
|
||||
return request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
||||
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
|
||||
@@ -112,6 +113,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous
|
||||
// executeAllFuzzingRules executes all fuzzing rules defined in template for a given base request
|
||||
func (request *Request) executeAllFuzzingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {
|
||||
applicable := false
|
||||
values = generators.MergeMaps(request.filterDataMap(input), values)
|
||||
for _, rule := range request.Fuzzing {
|
||||
select {
|
||||
case <-input.Context().Done():
|
||||
@@ -159,7 +161,7 @@ func (request *Request) executeAllFuzzingRules(input *contextargs.Context, value
|
||||
func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, input *contextargs.Context, callback protocols.OutputEventCallback) bool {
|
||||
hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
|
||||
hasInteractMarkers := len(gr.InteractURLs) > 0
|
||||
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input) {
|
||||
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) {
|
||||
return false
|
||||
}
|
||||
request.options.RateLimitTake()
|
||||
@@ -213,7 +215,7 @@ func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest,
|
||||
}
|
||||
if requestErr != nil {
|
||||
if request.options.HostErrorsCache != nil {
|
||||
request.options.HostErrorsCache.MarkFailed(input, requestErr)
|
||||
request.options.HostErrorsCache.MarkFailed(request.options.ProtocolType.String(), input, requestErr)
|
||||
}
|
||||
gologger.Verbose().Msgf("[%s] Error occurred in request: %s\n", request.options.TemplateID, requestErr)
|
||||
}
|
||||
@@ -234,7 +236,12 @@ func (request *Request) ShouldFuzzTarget(input *contextargs.Context) bool {
|
||||
}
|
||||
status := []bool{}
|
||||
for index, filter := range request.FuzzPreCondition {
|
||||
isMatch, _ := request.Match(request.filterDataMap(input), filter)
|
||||
dataMap := request.filterDataMap(input)
|
||||
// dump if svd is enabled
|
||||
if request.options.Options.ShowVarDump {
|
||||
gologger.Debug().Msgf("Fuzz Filter Variables: \n%s\n", vardump.DumpVariables(dataMap))
|
||||
}
|
||||
isMatch, _ := request.Match(dataMap, filter)
|
||||
status = append(status, isMatch)
|
||||
if request.options.Options.MatcherStatus {
|
||||
gologger.Debug().Msgf("[%s] [%s] Filter => %s : %v", input.MetaInput.Target(), request.options.TemplateID, operators.GetMatcherName(filter, index), isMatch)
|
||||
@@ -295,10 +302,5 @@ func (request *Request) filterDataMap(input *contextargs.Context) map[string]int
|
||||
// add default method value
|
||||
m["method"] = http.MethodGet
|
||||
}
|
||||
|
||||
// dump if svd is enabled
|
||||
if request.options.Options.ShowVarDump {
|
||||
gologger.Debug().Msgf("Fuzz Filter Variables: \n%s\n", vardump.DumpVariables(m))
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -766,6 +766,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
|
||||
@@ -100,13 +100,14 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
fields.Ip = types.ToString(wrapped.InternalEvent["ip"])
|
||||
}
|
||||
data := &output.ResultEvent{
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
URL: fields.URL,
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
URL: fields.URL,
|
||||
Matched: types.ToString(wrapped.InternalEvent["matched"]),
|
||||
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
|
||||
Metadata: wrapped.OperatorsResult.PayloadValues,
|
||||
|
||||
@@ -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
|
||||
@@ -504,14 +504,14 @@ func (request *Request) markUnresponsiveAddress(input *contextargs.Context, err
|
||||
return
|
||||
}
|
||||
if request.options.HostErrorsCache != nil {
|
||||
request.options.HostErrorsCache.MarkFailed(input, err)
|
||||
request.options.HostErrorsCache.MarkFailed(request.options.ProtocolType.String(), input, err)
|
||||
}
|
||||
}
|
||||
|
||||
// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history
|
||||
func (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {
|
||||
if request.options.HostErrorsCache != nil {
|
||||
return request.options.HostErrorsCache.Check(input)
|
||||
return request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Path: types.ToString(wrapped.InternalEvent["path"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["matched"]),
|
||||
|
||||
@@ -61,6 +61,8 @@ type ExecutorOptions struct {
|
||||
TemplatePath string
|
||||
// TemplateInfo contains information block of the template request
|
||||
TemplateInfo model.Info
|
||||
// TemplateVerifier is the verifier for the template
|
||||
TemplateVerifier string
|
||||
// RawTemplate is the raw template for the request
|
||||
RawTemplate []byte
|
||||
// Output is a writer interface for writing output events from executer.
|
||||
|
||||
@@ -347,11 +347,33 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
||||
// description. Multiple definitions are separated by commas.
|
||||
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
|
||||
var RequestPartDefinitions = map[string]string{
|
||||
"type": "Type is the type of request made",
|
||||
"response": "JSON SSL protocol handshake details",
|
||||
"not_after": "Timestamp after which the remote cert expires",
|
||||
"host": "Host is the input to the template",
|
||||
"matched": "Matched is the input which was matched upon",
|
||||
"template-id": "ID of the template executed",
|
||||
"template-info": "Info Block of the template executed",
|
||||
"template-path": "Path of the template executed",
|
||||
"host": "Host is the input to the template",
|
||||
"port": "Port is the port of the host",
|
||||
"matched": "Matched is the input which was matched upon",
|
||||
"type": "Type is the type of request made",
|
||||
"timestamp": "Timestamp is the time when the request was made",
|
||||
"response": "JSON SSL protocol handshake details",
|
||||
"cipher": "Cipher is the encryption algorithm used",
|
||||
"domains": "Domains are the list of domain names in the certificate",
|
||||
"fingerprint_hash": "Fingerprint hash is the unique identifier of the certificate",
|
||||
"ip": "IP is the IP address of the server",
|
||||
"issuer_cn": "Issuer CN is the common name of the certificate issuer",
|
||||
"issuer_dn": "Issuer DN is the distinguished name of the certificate issuer",
|
||||
"issuer_org": "Issuer organization is the organization of the certificate issuer",
|
||||
"not_after": "Timestamp after which the remote cert expires",
|
||||
"not_before": "Timestamp before which the certificate is not valid",
|
||||
"probe_status": "Probe status indicates if the probe was successful",
|
||||
"serial": "Serial is the serial number of the certificate",
|
||||
"sni": "SNI is the server name indication used in the handshake",
|
||||
"subject_an": "Subject AN is the list of subject alternative names",
|
||||
"subject_cn": "Subject CN is the common name of the certificate subject",
|
||||
"subject_dn": "Subject DN is the distinguished name of the certificate subject",
|
||||
"subject_org": "Subject organization is the organization of the certificate subject",
|
||||
"tls_connection": "TLS connection is the type of TLS connection used",
|
||||
"tls_version": "TLS version is the version of the TLS protocol used",
|
||||
}
|
||||
|
||||
// Match performs matching operation for a matcher on model and returns:
|
||||
@@ -398,6 +420,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
|
||||
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
|
||||
Info: wrapped.InternalEvent["template-info"].(model.Info),
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
|
||||
@@ -402,6 +402,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(request.options.TemplateID),
|
||||
TemplatePath: types.ToString(request.options.TemplatePath),
|
||||
Info: request.options.TemplateInfo,
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: fields.Host,
|
||||
Port: fields.Port,
|
||||
|
||||
@@ -177,6 +177,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||
TemplateID: types.ToString(request.options.TemplateID),
|
||||
TemplatePath: types.ToString(request.options.TemplatePath),
|
||||
Info: request.options.TemplateInfo,
|
||||
TemplateVerifier: request.options.TemplateVerifier,
|
||||
Type: types.ToString(wrapped.InternalEvent["type"]),
|
||||
Host: types.ToString(wrapped.InternalEvent["host"]),
|
||||
Metadata: wrapped.OperatorsResult.PayloadValues,
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
options *Options
|
||||
mutex *sync.Mutex
|
||||
rows []output.ResultEvent
|
||||
collection *mongo.Collection
|
||||
connection *mongo.Client
|
||||
}
|
||||
|
||||
// Options contains the configuration options for MongoDB exporter client
|
||||
type Options struct {
|
||||
// ConnectionString is the connection string to the MongoDB database
|
||||
ConnectionString string `yaml:"connection-string"`
|
||||
// CollectionName is the name of the MongoDB collection in which to store the results
|
||||
CollectionName string `yaml:"collection-name"`
|
||||
// OmitRaw excludes the Request and Response from the results (helps with filesize)
|
||||
OmitRaw bool `yaml:"omit-raw"`
|
||||
// BatchSize determines the number of results to be kept in memory before writing it to the database or 0 to
|
||||
// persist all in memory and write all results at the end (default)
|
||||
BatchSize int `yaml:"batch-size"`
|
||||
}
|
||||
|
||||
// New creates a new MongoDB exporter integration client based on options.
|
||||
func New(options *Options) (*Exporter, error) {
|
||||
exporter := &Exporter{
|
||||
mutex: &sync.Mutex{},
|
||||
options: options,
|
||||
rows: []output.ResultEvent{},
|
||||
}
|
||||
|
||||
// If the environment variable for the connection string is set, then use that instead. This allows for easier
|
||||
// management of sensitive items such as credentials
|
||||
envConnectionString := os.Getenv("MONGO_CONNECTION_STRING")
|
||||
if envConnectionString != "" {
|
||||
options.ConnectionString = envConnectionString
|
||||
gologger.Info().Msgf("Using connection string from environment variable MONGO_CONNECTION_STRING")
|
||||
}
|
||||
|
||||
// Create the connection to the database
|
||||
clientOptions := mongooptions.Client().ApplyURI(options.ConnectionString)
|
||||
|
||||
// Create a new client and connect to the MongoDB server
|
||||
client, err := mongo.Connect(context.TODO(), clientOptions)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Error creating MongoDB client: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the connection is valid
|
||||
err = client.Ping(context.Background(), nil)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Error connecting to MongoDB: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the database from the connection string to set the database and collection
|
||||
parsed, err := url.Parse(options.ConnectionString)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Error parsing connection string: %s", options.ConnectionString)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databaseName := strings.TrimPrefix(parsed.Path, "/")
|
||||
|
||||
if databaseName == "" {
|
||||
return nil, errors.New("error getting database name from connection string")
|
||||
}
|
||||
|
||||
exporter.connection = client
|
||||
exporter.collection = client.Database(databaseName).Collection(options.CollectionName)
|
||||
|
||||
return exporter, nil
|
||||
}
|
||||
|
||||
// Export writes a result document to the configured MongoDB collection
|
||||
// in the database configured by the connection string
|
||||
func (exporter *Exporter) Export(event *output.ResultEvent) error {
|
||||
exporter.mutex.Lock()
|
||||
defer exporter.mutex.Unlock()
|
||||
|
||||
if exporter.options.OmitRaw {
|
||||
event.Request = ""
|
||||
event.Response = ""
|
||||
}
|
||||
|
||||
// Add the row to the queue to be processed
|
||||
exporter.rows = append(exporter.rows, *event)
|
||||
|
||||
// If the batch size is greater than 0 and the number of rows has reached the batch, flush it to the database
|
||||
if exporter.options.BatchSize > 0 && len(exporter.rows) >= exporter.options.BatchSize {
|
||||
err := exporter.WriteRows()
|
||||
if err != nil {
|
||||
// The error is already logged, return it to bubble up to the caller
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRows writes all rows from the rows list to the MongoDB collection and removes them from the list
|
||||
func (exporter *Exporter) WriteRows() error {
|
||||
// Loop through the rows and write them, removing them as they're entered
|
||||
for len(exporter.rows) > 0 {
|
||||
data := exporter.rows[0]
|
||||
|
||||
// Write the data to the database
|
||||
_, err := exporter.collection.InsertOne(context.TODO(), data)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Error inserting record into MongoDB collection: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the item from the list
|
||||
exporter.rows = exporter.rows[1:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (exporter *Exporter) Close() error {
|
||||
exporter.mutex.Lock()
|
||||
defer exporter.mutex.Unlock()
|
||||
|
||||
// Write all pending rows
|
||||
err := exporter.WriteRows()
|
||||
if err != nil {
|
||||
// The error is already logged, return it to bubble up to the caller
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the database connection
|
||||
err = exporter.connection.Disconnect(context.TODO())
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Error disconnecting from MongoDB: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
|
||||
@@ -12,6 +13,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 +31,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
|
||||
@@ -41,6 +45,8 @@ type Options struct {
|
||||
JSONExporter *jsonexporter.Options `yaml:"json"`
|
||||
// JSONLExporter contains configuration options for JSONL Exporter Module
|
||||
JSONLExporter *jsonl.Options `yaml:"jsonl"`
|
||||
// MongoDBExporter containers the configuration options for the MongoDB Exporter Module
|
||||
MongoDBExporter *mongo.Options `yaml:"mongodb"`
|
||||
|
||||
HttpClient *retryablehttp.Client `yaml:"-"`
|
||||
OmitRaw bool `yaml:"-"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package reporting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -28,6 +29,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 +114,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 {
|
||||
@@ -156,6 +167,13 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) {
|
||||
}
|
||||
client.exporters = append(client.exporters, exporter)
|
||||
}
|
||||
if options.MongoDBExporter != nil {
|
||||
exporter, err := mongo.New(options.MongoDBExporter)
|
||||
if err != nil {
|
||||
return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation)
|
||||
}
|
||||
client.exporters = append(client.exporters, exporter)
|
||||
}
|
||||
|
||||
if doNotDedupe {
|
||||
return client, nil
|
||||
@@ -195,12 +213,14 @@ func CreateConfigIfNotExists() error {
|
||||
GitLab: &gitlab.Options{},
|
||||
Gitea: &gitea.Options{},
|
||||
Jira: &jira.Options{},
|
||||
Linear: &linear.Options{},
|
||||
MarkdownExporter: &markdown.Options{},
|
||||
SarifExporter: &sarif.Options{},
|
||||
ElasticsearchExporter: &es.Options{},
|
||||
SplunkExporter: &splunk.Options{},
|
||||
JSONExporter: &json_exporter.Options{},
|
||||
JSONLExporter: &jsonl.Options{},
|
||||
MongoDBExporter: &mongo.Options{},
|
||||
}
|
||||
reportingFile, err := os.Create(reportingConfig)
|
||||
if err != nil {
|
||||
@@ -240,7 +260,7 @@ func (c *ReportingClient) Close() {
|
||||
if failed > 0 {
|
||||
msgBuilder.WriteString(fmt.Sprintf(", %d failed", failed))
|
||||
}
|
||||
gologger.Info().Msgf(msgBuilder.String())
|
||||
gologger.Info().Msgf("%v", msgBuilder.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIss
|
||||
if label := i.options.IssueLabel; label != "" {
|
||||
labels = append(labels, label)
|
||||
}
|
||||
customLabels := gitlab.Labels(labels)
|
||||
customLabels := gitlab.LabelOptions(labels)
|
||||
assigneeIDs := []int{i.userID}
|
||||
if i.options.DuplicateIssueCheck {
|
||||
searchIn := "title"
|
||||
|
||||
@@ -70,9 +70,13 @@ type Options struct {
|
||||
// Token is the token for jira instance.
|
||||
Token string `yaml:"token" json:"token" validate:"required"`
|
||||
// ProjectName is the name of the project.
|
||||
ProjectName string `yaml:"project-name" json:"project_name" validate:"required"`
|
||||
ProjectName string `yaml:"project-name" json:"project_name"`
|
||||
// ProjectID is the ID of the project (optional)
|
||||
ProjectID string `yaml:"project-id" json:"project_id"`
|
||||
// IssueType (optional) is the name of the created issue type
|
||||
IssueType string `yaml:"issue-type" json:"issue_type"`
|
||||
// IssueTypeID (optional) is the ID of the created issue type
|
||||
IssueTypeID string `yaml:"issue-type-id" json:"issue_type_id"`
|
||||
// SeverityAsLabel (optional) sends the severity as the label of the created
|
||||
// issue.
|
||||
SeverityAsLabel bool `yaml:"severity-as-label" json:"severity_as_label"`
|
||||
@@ -183,6 +187,7 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
|
||||
Project: jira.Project{Key: i.options.ProjectName},
|
||||
Summary: summary,
|
||||
}
|
||||
|
||||
// On-prem version of Jira server does not use AccountID
|
||||
if !i.options.Cloud {
|
||||
fields = &jira.IssueFields{
|
||||
@@ -195,6 +200,12 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
|
||||
Unknowns: customFields,
|
||||
}
|
||||
}
|
||||
if i.options.IssueTypeID != "" {
|
||||
fields.Type = jira.IssueType{ID: i.options.IssueTypeID}
|
||||
}
|
||||
if i.options.ProjectID != "" {
|
||||
fields.Project = jira.Project{ID: i.options.ProjectID}
|
||||
}
|
||||
|
||||
issueData := &jira.Issue{
|
||||
Fields: fields,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user