mirror of https://github.com/daffainfo/nuclei.git
Merge branch 'dev' into remote-template-workflow-lists
# Conflicts: # v2/cmd/nuclei/main.godev
commit
86cf09fa3f
|
@ -0,0 +1,15 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
|
||||||
|
- name: Ask an question / advise on using nuclei
|
||||||
|
url: https://github.com/projectdiscovery/nuclei/discussions/categories/q-a
|
||||||
|
about: Ask a question or request support for using nuclei
|
||||||
|
|
||||||
|
- name: Share idea / feature to discuss for nuclei
|
||||||
|
url: https://github.com/projectdiscovery/nuclei/discussions/categories/ideas
|
||||||
|
about: Share idea / feature to discuss for nuclei
|
||||||
|
|
||||||
|
- name: Connect with PD Team (Discord)
|
||||||
|
url: https://discord.gg/projectdiscovery
|
||||||
|
about: Connect with PD Team for direct communication
|
|
@ -1,14 +1,21 @@
|
||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Request feature to implement in this project
|
||||||
title: "[feature]"
|
title: ""
|
||||||
labels: ''
|
labels: 'Type: Enhancement'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
<!--
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
### Please describe your feature request:
|
||||||
A clear and concise description of what you want to happen.
|
<!-- 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,18 +1,36 @@
|
||||||
---
|
---
|
||||||
name: Issue report
|
name: Issue report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us to improve the project
|
||||||
title: "[issue]"
|
labels: 'Type: Bug'
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
<!--
|
||||||
A clear and concise description of what the bug is.
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
**Nuclei version**
|
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
|
||||||
Please share the version of the nuclei you are running with `nuclei -version`
|
|
||||||
|
### 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/ -->
|
||||||
|
|
||||||
|
### 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...
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
**Screenshot of the error or bug**
|
### Anything else:
|
||||||
please add the screenshot showing bug or issue you are facing.
|
<!-- Links? References? Screnshots? Anything that will give us more context about the issue that you are encountering! -->
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
## Proposed changes
|
||||||
|
|
||||||
|
<!-- Describe the overall picture of your modifications to help maintainers understand the pull request. PRs are required to be associated to their related issue tickets or feature request. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
<!-- Put an "x" in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. -->
|
||||||
|
|
||||||
|
- [ ] Pull request is created against the [dev](https://github.com/projectdiscovery/nuclei/tree/dev) branch
|
||||||
|
- [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes
|
||||||
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
|
- [ ] I have added necessary documentation (if appropriate)
|
|
@ -1,30 +1,26 @@
|
||||||
name: 🎉 Release Binary
|
name: 🎉 Release Binary
|
||||||
on:
|
on:
|
||||||
create:
|
create:
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
- uses: actions/checkout@v2
|
||||||
name: "Check out code"
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
|
||||||
name: "Set up Go"
|
- uses: actions/setup-go@v2
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.17
|
||||||
-
|
|
||||||
env:
|
- uses: goreleaser/goreleaser-action@v2
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
name: "Create release on GitHub"
|
|
||||||
uses: goreleaser/goreleaser-action@v2
|
|
||||||
with:
|
with:
|
||||||
args: "release --rm-dist"
|
args: "release --rm-dist"
|
||||||
version: latest
|
version: latest
|
||||||
workdir: v2/
|
workdir: v2/
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
77
README.md
77
README.md
|
@ -44,6 +44,8 @@ We have a [dedicated repository](https://github.com/projectdiscovery/nuclei-temp
|
||||||
|
|
||||||
# Install Nuclei
|
# Install Nuclei
|
||||||
|
|
||||||
|
Nuclei requires **go1.17** to install successfully. Run the following command to install the latest version -
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest
|
go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest
|
||||||
```
|
```
|
||||||
|
@ -88,30 +90,29 @@ TARGET:
|
||||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||||
|
|
||||||
TEMPLATES:
|
TEMPLATES:
|
||||||
-tl list all available templates
|
|
||||||
-t, -templates string[] template or template directory paths to include in the scan
|
-t, -templates string[] template or template directory paths to include in the scan
|
||||||
-w, -workflows string[] list of workflows to run
|
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||||
-nt, -new-templates run newly added templates only
|
-w, -workflows string[] workflow or workflow directory paths to include in the scan
|
||||||
-validate validate the passed templates to nuclei
|
-validate validate the passed templates to nuclei
|
||||||
|
-tl list all available templates
|
||||||
|
|
||||||
FILTERING:
|
FILTERING:
|
||||||
-tags string[] execute a subset of templates that contain the provided tags
|
-tags string[] execute a subset of templates that contain the provided tags
|
||||||
-include-tags string[] tags from the default deny list that permit executing more intrusive templates
|
-etags, -exclude-tags string[] exclude templates with the provided tags
|
||||||
-etags, -exclude-tags string[] exclude templates with the provided tags
|
-itags, -include-tags string[] tags from the default deny list that permit executing more intrusive templates
|
||||||
-include-templates string[] templates to be executed even if they are excluded either by default or configuration
|
-et, -exclude-templates string[] template or template directory paths to exclude
|
||||||
-exclude-templates, -exclude string[] template or template directory paths to exclude
|
-it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration
|
||||||
-severity, -impact value[] Templates to run based on severity. Possible values: info, low, medium, high, critical
|
-s, -severity value[] Templates to run based on severity. Possible values - info,low,medium,high,critical
|
||||||
-author string[] execute templates that are (co-)created by the specified authors
|
-es, -exclude-severity value[] Templates to exclude based on severity. Possible values - info,low,medium,high,critical
|
||||||
|
-a, -author string[] execute templates that are (co-)created by the specified authors
|
||||||
|
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
-o, -output string output file to write found issues/vulnerabilities
|
-o, -output string output file to write found issues/vulnerabilities
|
||||||
-silent display findings only
|
-silent display findings only
|
||||||
-v, -verbose show verbose output
|
|
||||||
-vv display extra verbose information
|
|
||||||
-nc, -no-color disable output content coloring (ANSI escape codes)
|
-nc, -no-color disable output content coloring (ANSI escape codes)
|
||||||
-json write output in JSONL(ines) format
|
-json write output in JSONL(ines) format
|
||||||
-irr, -include-rr include request/response pairs in the JSONL output (for findings only)
|
-irr, -include-rr include request/response pairs in the JSONL output (for findings only)
|
||||||
-nm, -no-meta don't display match metadata in CLI output
|
-nm, -no-meta don't display match metadata
|
||||||
-nts, -no-timestamp don't display timestamp metadata in CLI output
|
-nts, -no-timestamp don't display timestamp metadata in CLI output
|
||||||
-rdb, -report-db string local nuclei reporting database (always use this to persist report data)
|
-rdb, -report-db string local nuclei reporting database (always use this to persist report data)
|
||||||
-me, -markdown-export string directory to export results in markdown format
|
-me, -markdown-export string directory to export results in markdown format
|
||||||
|
@ -123,37 +124,39 @@ CONFIGURATIONS:
|
||||||
-H, -header string[] custom headers in header:value format
|
-H, -header string[] custom headers in header:value format
|
||||||
-V, -var value custom vars in var=value format
|
-V, -var value custom vars in var=value format
|
||||||
-r, -resolvers string file containing resolver list for nuclei
|
-r, -resolvers string file containing resolver list for nuclei
|
||||||
-system-resolvers use system DNS resolving as error fallback
|
-sr, -system-resolvers use system DNS resolving as error fallback
|
||||||
-passive enable passive HTTP response processing mode
|
-passive enable passive HTTP response processing mode
|
||||||
-env-vars enable environment variables support
|
-ev, -env-vars enable environment variables to be used in template
|
||||||
|
|
||||||
INTERACTSH:
|
INTERACTSH:
|
||||||
-no-interactsh disable interactsh server for OOB testing
|
-iserver, -interactsh-server string interactsh server url for self-hosted instance (default "https://interactsh.com")
|
||||||
-interactsh-url string interactsh server url for self-hosted instance (default "https://interactsh.com")
|
-itoken, -interactsh-token string authentication token for self-hosted interactsh server
|
||||||
-interactsh-token string authentication token for self-hosted interactsh server
|
-interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
|
||||||
-interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
|
-interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
|
||||||
-interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
|
-interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
|
||||||
-interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
|
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
|
||||||
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
|
-ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates
|
||||||
|
|
||||||
RATE-LIMIT:
|
RATE-LIMIT:
|
||||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute
|
-rlm, -rate-limit-minute int maximum number of requests to send per minute
|
||||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 10)
|
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||||
|
|
||||||
OPTIMIZATIONS:
|
OPTIMIZATIONS:
|
||||||
-timeout int time to wait in seconds before timeout (default 5)
|
-timeout int time to wait in seconds before timeout (default 5)
|
||||||
-retries int number of times to retry a failed request (default 1)
|
-retries int number of times to retry a failed request (default 1)
|
||||||
-max-host-error int max errors for a host before skipping from scan (default 30)
|
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
|
||||||
-project use a project folder to avoid sending same request multiple times
|
-project use a project folder to avoid sending same request multiple times
|
||||||
-project-path string set a specific project path (default "$TMPDIR/")
|
-project-path string set a specific project path
|
||||||
-spm, -stop-at-first-path stop processing HTTP requests after the first match (may break template/workflow logic)
|
-spm, -stop-at-first-path stop processing HTTP requests after the first match (may break template/workflow logic)
|
||||||
|
-stream Stream mode - start elaborating without sorting the input
|
||||||
|
|
||||||
HEADLESS:
|
HEADLESS:
|
||||||
-headless enable templates that require headless browser support
|
-headless enable templates that require headless browser support
|
||||||
-page-timeout int seconds to wait for each page in headless mode (default 20)
|
-page-timeout int seconds to wait for each page in headless mode (default 20)
|
||||||
-show-browser show the browser on the screen when running templates with headless mode
|
-sb, -show-browser show the browser on the screen when running templates with headless mode
|
||||||
|
-sc, -system-chrome Use local installed chrome browser instead of nuclei installed
|
||||||
|
|
||||||
DEBUG:
|
DEBUG:
|
||||||
-debug show all requests and responses
|
-debug show all requests and responses
|
||||||
|
@ -161,22 +164,24 @@ DEBUG:
|
||||||
-debug-resp show all received responses
|
-debug-resp show all received responses
|
||||||
-proxy, -proxy-url string URL of the HTTP proxy server
|
-proxy, -proxy-url string URL of the HTTP proxy server
|
||||||
-proxy-socks-url string URL of the SOCKS proxy server
|
-proxy-socks-url string URL of the SOCKS proxy server
|
||||||
-trace-log string file to write sent requests trace log
|
-tlog, -trace-log string file to write sent requests trace log
|
||||||
-version show nuclei version
|
-version show nuclei version
|
||||||
|
-v, -verbose show verbose output
|
||||||
|
-vv display extra verbose information
|
||||||
-tv, -templates-version shows the version of the installed nuclei-templates
|
-tv, -templates-version shows the version of the installed nuclei-templates
|
||||||
|
|
||||||
UPDATE:
|
UPDATE:
|
||||||
-update update nuclei to the latest released version
|
-update update nuclei engine to the latest released version
|
||||||
-ut, -update-templates update the community templates to latest released version
|
-ut, -update-templates update nuclei-templates to latest released version
|
||||||
-nut, -no-update-templates do not check for nuclei-templates updates
|
-ud, -update-directory string overwrite the default directory to install nuclei-templates
|
||||||
-ud, -update-directory string overwrite the default nuclei-templates directory (default "$HOME/nuclei-templates")
|
-duc, -disable-update-check disable automatic nuclei/templates update check
|
||||||
|
|
||||||
STATISTICS:
|
STATISTICS:
|
||||||
-stats display statistics about the running scan
|
-stats display statistics about the running scan
|
||||||
-stats-json write statistics data to an output file in JSONL(ines) format
|
-sj, -stats-json write statistics data to an output file in JSONL(ines) format
|
||||||
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
|
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
|
||||||
-metrics expose nuclei metrics on a port
|
-m, -metrics expose nuclei metrics on a port
|
||||||
-metrics-port int port to expose nuclei metrics on (default 9092)
|
-mp, -metrics-port int port to expose nuclei metrics on (default 9092)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Nuclei
|
### Running Nuclei
|
||||||
|
|
|
@ -230,6 +230,19 @@ Workflows is a list of workflows to execute for a template.
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<div class="dd">
|
||||||
|
|
||||||
|
<code>self-contained</code> <i>bool</i>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="dt">
|
||||||
|
|
||||||
|
Self Contained marks Requests for the template as self-contained
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -823,14 +836,14 @@ in a combined manner allowing multirequest based matchers.
|
||||||
|
|
||||||
Attack is the type of payload combinations to perform.
|
Attack is the type of payload combinations to perform.
|
||||||
|
|
||||||
Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
|
batteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||||
permutations and combinations for all payloads.
|
permutations and combinations for all payloads.
|
||||||
|
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
|
|
||||||
- <code>sniper</code>
|
- <code>batteringram</code>
|
||||||
|
|
||||||
- <code>pitchfork</code>
|
- <code>pitchfork</code>
|
||||||
|
|
||||||
|
@ -2312,14 +2325,14 @@ host:
|
||||||
|
|
||||||
Attack is the type of payload combinations to perform.
|
Attack is the type of payload combinations to perform.
|
||||||
|
|
||||||
Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
|
Batteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||||
permutations and combinations for all payloads.
|
permutations and combinations for all payloads.
|
||||||
|
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
|
|
||||||
- <code>sniper</code>
|
- <code>batteringram</code>
|
||||||
|
|
||||||
- <code>pitchfork</code>
|
- <code>pitchfork</code>
|
||||||
|
|
||||||
|
@ -2379,6 +2392,31 @@ read-size: 2048
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="dd">
|
||||||
|
|
||||||
|
<code>read-all</code> <i>bool</i>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="dt">
|
||||||
|
|
||||||
|
ReadAll determines if the data stream should be read till the end regardless of the size
|
||||||
|
|
||||||
|
Default value for read-all is false.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
read-all: false
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
id: interactsh-integration-test
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: Interactsh Integration Test
|
||||||
|
author: pdteam
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
requests:
|
||||||
|
- method: GET
|
||||||
|
path:
|
||||||
|
- "{{BaseURL}}"
|
||||||
|
headers:
|
||||||
|
url: 'http://{{interactsh-url}}'
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
part: interactsh_protocol # Confirms the HTTP Interaction
|
||||||
|
words:
|
||||||
|
- "http"
|
|
@ -0,0 +1,18 @@
|
||||||
|
id: example-self-contained-input
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: example-self-contained
|
||||||
|
author: pd-team
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
self-contained: true
|
||||||
|
requests:
|
||||||
|
- raw:
|
||||||
|
- |
|
||||||
|
GET http://localhost:5431/ HTTP/1.1
|
||||||
|
Host: {{Hostname}}
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- This is self-contained response
|
|
@ -0,0 +1,16 @@
|
||||||
|
id: example-self-contained-input
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: example-self-contained
|
||||||
|
author: pd-team
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
self-contained: true
|
||||||
|
network:
|
||||||
|
- host:
|
||||||
|
- "localhost:5431"
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- "Authentication successful"
|
|
@ -606,7 +606,7 @@
|
||||||
},
|
},
|
||||||
"attack": {
|
"attack": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"sniper",
|
"batteringram",
|
||||||
"pitchfork",
|
"pitchfork",
|
||||||
"clusterbomb"
|
"clusterbomb"
|
||||||
],
|
],
|
||||||
|
@ -777,7 +777,7 @@
|
||||||
},
|
},
|
||||||
"attack": {
|
"attack": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"sniper",
|
"batteringram",
|
||||||
"pitchfork",
|
"pitchfork",
|
||||||
"clusterbomb"
|
"clusterbomb"
|
||||||
],
|
],
|
||||||
|
@ -809,6 +809,11 @@
|
||||||
"title": "size of network response to read",
|
"title": "size of network response to read",
|
||||||
"description": "Size of response to read at the end. Default is 1024 bytes"
|
"description": "Size of response to read at the end. Default is 1024 bytes"
|
||||||
},
|
},
|
||||||
|
"read-all": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "read all response stream",
|
||||||
|
"description": "Read all response stream till the server stops sending"
|
||||||
|
},
|
||||||
"matchers": {
|
"matchers": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/matchers.Matcher"
|
"$ref": "#/definitions/matchers.Matcher"
|
||||||
|
@ -845,6 +850,7 @@
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
|
"pattern": "^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "id of the template",
|
"title": "id of the template",
|
||||||
"description": "The Unique ID for the template",
|
"description": "The Unique ID for the template",
|
||||||
|
@ -911,6 +917,11 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "list of workflows to execute",
|
"title": "list of workflows to execute",
|
||||||
"description": "List of workflows to execute for template"
|
"description": "List of workflows to execute for template"
|
||||||
|
},
|
||||||
|
"self-contained": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "mark requests as self-contained",
|
||||||
|
"description": "Mark Requests for the template as self-contained"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
|
@ -7,7 +7,7 @@ echo 'Building Nuclei binary from current branch'
|
||||||
go build -o nuclei_dev ../nuclei
|
go build -o nuclei_dev ../nuclei
|
||||||
|
|
||||||
echo 'Installing latest release of nuclei'
|
echo 'Installing latest release of nuclei'
|
||||||
GO111MODULE=on go get -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei
|
GO111MODULE=on go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei
|
||||||
|
|
||||||
echo 'Starting Nuclei functional test'
|
echo 'Starting Nuclei functional test'
|
||||||
./functional-test -main nuclei -dev ./nuclei_dev -testcases testcases.txt
|
./functional-test -main nuclei -dev ./nuclei_dev -testcases testcases.txt
|
|
@ -31,6 +31,34 @@ var httpTestcases = map[string]testutils.TestCase{
|
||||||
"http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{},
|
"http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{},
|
||||||
"http/request-condition.yaml": &httpRequestCondition{},
|
"http/request-condition.yaml": &httpRequestCondition{},
|
||||||
"http/request-condition-new.yaml": &httpRequestCondition{},
|
"http/request-condition-new.yaml": &httpRequestCondition{},
|
||||||
|
"http/interactsh.yaml": &httpInteractshRequest{},
|
||||||
|
"http/self-contained.yaml": &httpRequestSelContained{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpInteractshRequest struct{}
|
||||||
|
|
||||||
|
// Executes executes a test case and returns an error if occurred
|
||||||
|
func (h *httpInteractshRequest) Execute(filePath string) error {
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
value := r.Header.Get("url")
|
||||||
|
if value != "" {
|
||||||
|
if resp, _ := http.DefaultClient.Get(value); resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
ts := httptest.NewServer(router)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(results) != 1 {
|
||||||
|
return errIncorrectResultsCount(results)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpGetHeaders struct{}
|
type httpGetHeaders struct{}
|
||||||
|
@ -493,3 +521,35 @@ func (h *httpRequestCondition) Execute(filePath string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpRequestSelContained struct{}
|
||||||
|
|
||||||
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
func (h *httpRequestSelContained) Execute(filePath string) error {
|
||||||
|
router := httprouter.New()
|
||||||
|
var routerErr error
|
||||||
|
|
||||||
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
_, _ = w.Write([]byte("This is self-contained response"))
|
||||||
|
})
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("localhost:%d", defaultStaticPort),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = server.ListenAndServe()
|
||||||
|
}()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if routerErr != nil {
|
||||||
|
return routerErr
|
||||||
|
}
|
||||||
|
if len(results) != 1 {
|
||||||
|
return errIncorrectResultsCount(results)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var networkTestcases = map[string]testutils.TestCase{
|
var networkTestcases = map[string]testutils.TestCase{
|
||||||
"network/basic.yaml": &networkBasic{},
|
"network/basic.yaml": &networkBasic{},
|
||||||
"network/hex.yaml": &networkBasic{},
|
"network/hex.yaml": &networkBasic{},
|
||||||
"network/multi-step.yaml": &networkMultiStep{},
|
"network/multi-step.yaml": &networkMultiStep{},
|
||||||
|
"network/self-contained.yaml": &networkRequestSelContained{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultStaticPort = 5431
|
||||||
|
|
||||||
type networkBasic struct{}
|
type networkBasic struct{}
|
||||||
|
|
||||||
// Execute executes a test case and returns an error if occurred
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
@ -94,3 +97,28 @@ func (h *networkMultiStep) Execute(filePath string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type networkRequestSelContained struct{}
|
||||||
|
|
||||||
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
func (h *networkRequestSelContained) Execute(filePath string) error {
|
||||||
|
var routerErr error
|
||||||
|
|
||||||
|
ts := testutils.NewTCPServer(func(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, _ = conn.Write([]byte("Authentication successful"))
|
||||||
|
}, defaultStaticPort)
|
||||||
|
defer ts.Close()
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if routerErr != nil {
|
||||||
|
return routerErr
|
||||||
|
}
|
||||||
|
if len(results) != 1 {
|
||||||
|
return errIncorrectResultsCount(results)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
# github contains configuration options for github issue tracker
|
# github contains configuration options for github issue tracker
|
||||||
#github:
|
#github:
|
||||||
# # base-url is the optional self-hosted github application url
|
# # base-url (optional) is the self-hosted github application url
|
||||||
# base-url: ""
|
# base-url: ""
|
||||||
# # username is the username of the github user
|
# # username is the username of the github user
|
||||||
# username: ""
|
# username: ""
|
||||||
|
@ -17,12 +17,14 @@
|
||||||
# token: ""
|
# token: ""
|
||||||
# # project-name is the name of the repository.
|
# # project-name is the name of the repository.
|
||||||
# project-name: ""
|
# project-name: ""
|
||||||
# # issue-label is the label of the created issue type
|
# # issue-label (optional) is the label of the created issue type
|
||||||
# issue-label: ""
|
# issue-label: ""
|
||||||
|
# # severity-as-label (optional) sets the sevetiry as the label of the created issue type
|
||||||
|
# severity-as-label: false
|
||||||
|
|
||||||
# gitlab contains configuration options for gitlab issue tracker
|
# gitlab contains configuration options for gitlab issue tracker
|
||||||
#gitlab:
|
#gitlab:
|
||||||
# # base-url is the optional self-hosted gitlab application url
|
# # base-url (optional) is the self-hosted gitlab application url
|
||||||
# base-url: ""
|
# base-url: ""
|
||||||
# # username is the username of the gitlab user
|
# # username is the username of the gitlab user
|
||||||
# username: ""
|
# username: ""
|
||||||
|
@ -30,14 +32,16 @@
|
||||||
# token: ""
|
# token: ""
|
||||||
# # project-id is the ID of the repository.
|
# # project-id is the ID of the repository.
|
||||||
# project-id: ""
|
# project-id: ""
|
||||||
# # issue-label is the label of the created issue type
|
# # issue-label (optional) is the label of the created issue type
|
||||||
# issue-label: ""
|
# issue-label: ""
|
||||||
|
# # severity-as-label (optional) sets the sevetiry as the label of the created issue type
|
||||||
|
# severity-as-label: false
|
||||||
|
|
||||||
# jira contains configuration options for jira issue tracker
|
# jira contains configuration options for jira issue tracker
|
||||||
#jira:
|
#jira:
|
||||||
# # cloud is the boolean which tells if Jira instance is running in the cloud or on-prem version is used
|
# # cloud (optional) is the boolean which tells if Jira instance is running in the cloud or on-prem version is used
|
||||||
# cloud: true
|
# cloud: true
|
||||||
# # update-existing is the boolean which tells if the existing, opened issue should be updated or new one should be created
|
# # update-existing (optional) is the boolean which tells if the existing, opened issue should be updated or new one should be created
|
||||||
# update-existing: false
|
# update-existing: false
|
||||||
# # URL is the jira application url
|
# # URL is the jira application url
|
||||||
# url: ""
|
# url: ""
|
||||||
|
@ -60,9 +64,9 @@
|
||||||
# port: 9200
|
# port: 9200
|
||||||
# # IndexName is the name of the elasticsearch index
|
# # IndexName is the name of the elasticsearch index
|
||||||
# index-name: nuclei
|
# index-name: nuclei
|
||||||
# # SSL enables ssl for elasticsearch connection
|
# # SSL (optional) enables ssl for elasticsearch connection
|
||||||
# ssl: false
|
# ssl: false
|
||||||
# # SSLVerification disables SSL verification for elasticsearch
|
# # SSLVerification (optional) disables SSL verification for elasticsearch
|
||||||
# ssl-verification: false
|
# ssl-verification: false
|
||||||
# # Username for the elasticsearch instance
|
# # Username for the elasticsearch instance
|
||||||
# username: test
|
# username: test
|
||||||
|
|
|
@ -53,45 +53,35 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "templates", "Templates",
|
createGroup(flagSet, "templates", "Templates",
|
||||||
flagSet.BoolVar(&options.TemplateList, "tl", false, "list all available templates"),
|
|
||||||
|
|
||||||
flagSet.StringSliceVarP(&options.Templates, "templates", "t", []string{}, "template or template directory paths to include in the scan"),
|
flagSet.StringSliceVarP(&options.Templates, "templates", "t", []string{}, "template or template directory paths to include in the scan"),
|
||||||
flagSet.StringSliceVarP(&options.TemplateURLs, "template-urls", "tu", []string{}, "URL to a list of templates"),
|
flagSet.StringSliceVarP(&options.TemplateURLs, "template-urls", "tu", []string{}, "URL to a list of templates"),
|
||||||
flagSet.StringSliceVarP(&options.Workflows, "workflows", "w", []string{}, "list of workflows to run"),
|
flagSet.BoolVarP(&options.NewTemplates, "new-templates", "nt", false, "run only new templates added in latest nuclei-templates release"),
|
||||||
|
flagSet.StringSliceVarP(&options.Workflows, "workflows", "w", []string{}, "workflow or workflow directory paths to include in the scan"),
|
||||||
flagSet.StringSliceVarP(&options.WorkflowURLs, "workflow-urls", "wu", []string{}, "URL to a list of workflows to run"),
|
flagSet.StringSliceVarP(&options.WorkflowURLs, "workflow-urls", "wu", []string{}, "URL to a list of workflows to run"),
|
||||||
|
|
||||||
flagSet.BoolVarP(&options.NewTemplates, "new-templates", "nt", false, "run newly added templates only"),
|
|
||||||
flagSet.BoolVar(&options.Validate, "validate", false, "validate the passed templates to nuclei"),
|
flagSet.BoolVar(&options.Validate, "validate", false, "validate the passed templates to nuclei"),
|
||||||
|
flagSet.BoolVar(&options.TemplateList, "tl", false, "list all available templates"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "filters", "Filtering",
|
createGroup(flagSet, "filters", "Filtering",
|
||||||
flagSet.NormalizedStringSliceVar(&options.Tags, "tags", []string{}, "execute a subset of templates that contain the provided tags"),
|
flagSet.NormalizedStringSliceVar(&options.Tags, "tags", []string{}, "execute a subset of templates that contain the provided tags"),
|
||||||
flagSet.NormalizedStringSliceVar(&options.IncludeTags, "include-tags", []string{}, "tags from the default deny list that permit executing more intrusive templates"), // TODO show default deny list
|
flagSet.NormalizedStringSliceVarP(&options.IncludeTags, "include-tags", "itags", []string{}, "tags from the default deny list that permit executing more intrusive templates"), // TODO show default deny list
|
||||||
flagSet.NormalizedStringSliceVarP(&options.ExcludeTags, "exclude-tags", "etags", []string{}, "exclude templates with the provided tags"),
|
flagSet.NormalizedStringSliceVarP(&options.ExcludeTags, "exclude-tags", "etags", []string{}, "exclude templates with the provided tags"),
|
||||||
|
flagSet.StringSliceVarP(&options.IncludeTemplates, "include-templates", "it", []string{}, "templates to be executed even if they are excluded either by default or configuration"),
|
||||||
flagSet.StringSliceVar(&options.IncludeTemplates, "include-templates", []string{}, "templates to be executed even if they are excluded either by default or configuration"),
|
flagSet.StringSliceVarP(&options.ExcludedTemplates, "exclude-templates", "et", []string{}, "template or template directory paths to exclude"),
|
||||||
flagSet.StringSliceVarP(&options.ExcludedTemplates, "exclude", "exclude-templates", []string{}, "template or template directory paths to exclude"),
|
flagSet.VarP(&options.Severities, "severity", "s", fmt.Sprintf("Templates to run based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
|
||||||
|
flagSet.VarP(&options.ExcludeSeverities, "exclude-severity", "es", fmt.Sprintf("Templates to exclude based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
|
||||||
flagSet.VarP(&options.Severities, "impact", "severity", fmt.Sprintf("Templates to run based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
|
flagSet.NormalizedStringSliceVarP(&options.Author, "author", "a", []string{}, "execute templates that are (co-)created by the specified authors"),
|
||||||
flagSet.VarP(&options.ExcludeSeverities, "exclude-impact", "exclude-severity", fmt.Sprintf("Templates to exclude based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
|
|
||||||
flagSet.NormalizedStringSliceVar(&options.Author, "author", []string{}, "execute templates that are (co-)created by the specified authors"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "output", "Output",
|
createGroup(flagSet, "output", "Output",
|
||||||
flagSet.StringVarP(&options.Output, "output", "o", "", "output file to write found issues/vulnerabilities"),
|
flagSet.StringVarP(&options.Output, "output", "o", "", "output file to write found issues/vulnerabilities"),
|
||||||
|
|
||||||
flagSet.BoolVar(&options.Silent, "silent", false, "display findings only"),
|
flagSet.BoolVar(&options.Silent, "silent", false, "display findings only"),
|
||||||
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
|
|
||||||
flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display extra verbose information"),
|
|
||||||
flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable output content coloring (ANSI escape codes)"),
|
flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable output content coloring (ANSI escape codes)"),
|
||||||
|
|
||||||
flagSet.BoolVar(&options.JSON, "json", false, "write output in JSONL(ines) format"),
|
flagSet.BoolVar(&options.JSON, "json", false, "write output in JSONL(ines) format"),
|
||||||
flagSet.BoolVarP(&options.JSONRequests, "include-rr", "irr", false, "include request/response pairs in the JSONL output (for findings only)"),
|
flagSet.BoolVarP(&options.JSONRequests, "include-rr", "irr", false, "include request/response pairs in the JSONL output (for findings only)"),
|
||||||
|
|
||||||
flagSet.BoolVarP(&options.NoMeta, "no-meta", "nm", false, "don't display match metadata"),
|
flagSet.BoolVarP(&options.NoMeta, "no-meta", "nm", false, "don't display match metadata"),
|
||||||
flagSet.BoolVarP(&options.NoTimestamp, "no-timestamp", "nts", false, "don't display timestamp metadata in CLI output"),
|
flagSet.BoolVarP(&options.NoTimestamp, "no-timestamp", "nts", false, "don't display timestamp metadata in CLI output"),
|
||||||
flagSet.StringVarP(&options.ReportingDB, "report-db", "rdb", "", "local nuclei reporting database (always use this to persist report data)"),
|
flagSet.StringVarP(&options.ReportingDB, "report-db", "rdb", "", "local nuclei reporting database (always use this to persist report data)"),
|
||||||
|
|
||||||
flagSet.StringVarP(&options.MarkdownExportDirectory, "markdown-export", "me", "", "directory to export results in markdown format"),
|
flagSet.StringVarP(&options.MarkdownExportDirectory, "markdown-export", "me", "", "directory to export results in markdown format"),
|
||||||
flagSet.StringVarP(&options.SarifExport, "sarif-export", "se", "", "file to export results in SARIF format"),
|
flagSet.StringVarP(&options.SarifExport, "sarif-export", "se", "", "file to export results in SARIF format"),
|
||||||
)
|
)
|
||||||
|
@ -99,50 +89,46 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||||
createGroup(flagSet, "configs", "Configurations",
|
createGroup(flagSet, "configs", "Configurations",
|
||||||
flagSet.StringVar(&cfgFile, "config", "", "path to the nuclei configuration file"),
|
flagSet.StringVar(&cfgFile, "config", "", "path to the nuclei configuration file"),
|
||||||
flagSet.StringVarP(&options.ReportingConfig, "report-config", "rc", "", "nuclei reporting module configuration file"), // TODO merge into the config file or rename to issue-tracking
|
flagSet.StringVarP(&options.ReportingConfig, "report-config", "rc", "", "nuclei reporting module configuration file"), // TODO merge into the config file or rename to issue-tracking
|
||||||
|
|
||||||
flagSet.StringSliceVarP(&options.CustomHeaders, "header", "H", []string{}, "custom headers in header:value format"),
|
flagSet.StringSliceVarP(&options.CustomHeaders, "header", "H", []string{}, "custom headers in header:value format"),
|
||||||
|
|
||||||
flagSet.RuntimeMapVarP(&options.Vars, "var", "V", []string{}, "custom vars in var=value format"),
|
flagSet.RuntimeMapVarP(&options.Vars, "var", "V", []string{}, "custom vars in var=value format"),
|
||||||
|
|
||||||
flagSet.StringVarP(&options.ResolversFile, "resolvers", "r", "", "file containing resolver list for nuclei"),
|
flagSet.StringVarP(&options.ResolversFile, "resolvers", "r", "", "file containing resolver list for nuclei"),
|
||||||
flagSet.BoolVar(&options.SystemResolvers, "system-resolvers", false, "use system DNS resolving as error fallback"),
|
flagSet.BoolVarP(&options.SystemResolvers, "system-resolvers", "sr", false, "use system DNS resolving as error fallback"),
|
||||||
flagSet.BoolVar(&options.OfflineHTTP, "passive", false, "enable passive HTTP response processing mode"),
|
flagSet.BoolVar(&options.OfflineHTTP, "passive", false, "enable passive HTTP response processing mode"),
|
||||||
flagSet.BoolVar(&options.EnvironmentVariables, "env-vars", false, "enable environment variables support"),
|
flagSet.BoolVarP(&options.EnvironmentVariables, "env-vars", "ev", false, "enable environment variables to be used in template"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "interactsh", "interactsh",
|
createGroup(flagSet, "interactsh", "interactsh",
|
||||||
flagSet.BoolVar(&options.NoInteractsh, "no-interactsh", false, "disable interactsh server for OOB testing"),
|
flagSet.StringVarP(&options.InteractshURL, "interactsh-server", "iserver", "https://interactsh.com", "interactsh server url for self-hosted instance"),
|
||||||
flagSet.StringVar(&options.InteractshURL, "interactsh-url", "https://interactsh.com", "interactsh server url for self-hosted instance"),
|
flagSet.StringVarP(&options.InteractshToken, "interactsh-token", "itoken", "", "authentication token for self-hosted interactsh server"),
|
||||||
flagSet.StringVar(&options.InteractshToken, "interactsh-token", "", "authentication token for self-hosted interactsh server"),
|
|
||||||
flagSet.IntVar(&options.InteractionsCacheSize, "interactions-cache-size", 5000, "number of requests to keep in the interactions cache"),
|
flagSet.IntVar(&options.InteractionsCacheSize, "interactions-cache-size", 5000, "number of requests to keep in the interactions cache"),
|
||||||
flagSet.IntVar(&options.InteractionsEviction, "interactions-eviction", 60, "number of seconds to wait before evicting requests from cache"),
|
flagSet.IntVar(&options.InteractionsEviction, "interactions-eviction", 60, "number of seconds to wait before evicting requests from cache"),
|
||||||
flagSet.IntVar(&options.InteractionsPollDuration, "interactions-poll-duration", 5, "number of seconds to wait before each interaction poll request"),
|
flagSet.IntVar(&options.InteractionsPollDuration, "interactions-poll-duration", 5, "number of seconds to wait before each interaction poll request"),
|
||||||
flagSet.IntVar(&options.InteractionsColldownPeriod, "interactions-cooldown-period", 5, "extra time for interaction polling before exiting"),
|
flagSet.IntVar(&options.InteractionsColldownPeriod, "interactions-cooldown-period", 5, "extra time for interaction polling before exiting"),
|
||||||
|
flagSet.BoolVarP(&options.NoInteractsh, "no-interactsh", "ni", false, "disable interactsh server for OAST testing, exclude OAST based templates"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "rate-limit", "Rate-Limit",
|
createGroup(flagSet, "rate-limit", "Rate-Limit",
|
||||||
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
|
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
|
||||||
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute"),
|
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute"),
|
||||||
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
|
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
|
||||||
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 10, "maximum number of templates to be executed in parallel"),
|
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "optimization", "Optimizations",
|
createGroup(flagSet, "optimization", "Optimizations",
|
||||||
flagSet.IntVar(&options.Timeout, "timeout", 5, "time to wait in seconds before timeout"),
|
flagSet.IntVar(&options.Timeout, "timeout", 5, "time to wait in seconds before timeout"),
|
||||||
flagSet.IntVar(&options.Retries, "retries", 1, "number of times to retry a failed request"),
|
flagSet.IntVar(&options.Retries, "retries", 1, "number of times to retry a failed request"),
|
||||||
flagSet.IntVar(&options.MaxHostError, "max-host-error", 30, "max errors for a host before skipping from scan"),
|
flagSet.IntVarP(&options.MaxHostError, "max-host-error", "mhe", 30, "max errors for a host before skipping from scan"),
|
||||||
|
|
||||||
flagSet.BoolVar(&options.Project, "project", false, "use a project folder to avoid sending same request multiple times"),
|
flagSet.BoolVar(&options.Project, "project", false, "use a project folder to avoid sending same request multiple times"),
|
||||||
flagSet.StringVar(&options.ProjectPath, "project-path", os.TempDir(), "set a specific project path"),
|
flagSet.StringVar(&options.ProjectPath, "project-path", os.TempDir(), "set a specific project path"),
|
||||||
|
|
||||||
flagSet.BoolVarP(&options.StopAtFirstMatch, "stop-at-first-path", "spm", false, "stop processing HTTP requests after the first match (may break template/workflow logic)"),
|
flagSet.BoolVarP(&options.StopAtFirstMatch, "stop-at-first-path", "spm", false, "stop processing HTTP requests after the first match (may break template/workflow logic)"),
|
||||||
|
flagSet.BoolVar(&options.Stream, "stream", false, "Stream mode - start elaborating without sorting the input"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "headless", "Headless",
|
createGroup(flagSet, "headless", "Headless",
|
||||||
flagSet.BoolVar(&options.Headless, "headless", false, "enable templates that require headless browser support"),
|
flagSet.BoolVar(&options.Headless, "headless", false, "enable templates that require headless browser support"),
|
||||||
flagSet.IntVar(&options.PageTimeout, "page-timeout", 20, "seconds to wait for each page in headless mode"),
|
flagSet.IntVar(&options.PageTimeout, "page-timeout", 20, "seconds to wait for each page in headless mode"),
|
||||||
flagSet.BoolVar(&options.ShowBrowser, "show-browser", false, "show the browser on the screen when running templates with headless mode"),
|
flagSet.BoolVarP(&options.ShowBrowser, "show-browser", "sb", false, "show the browser on the screen when running templates with headless mode"),
|
||||||
flagSet.BoolVar(&options.UseInstalledChrome, "system-chrome", false, "Use local installed chrome browser instead of nuclei installed"),
|
flagSet.BoolVarP(&options.UseInstalledChrome, "system-chrome", "sc", false, "Use local installed chrome browser instead of nuclei installed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "debug", "Debug",
|
createGroup(flagSet, "debug", "Debug",
|
||||||
|
@ -154,27 +140,26 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||||
TODO should auto-set the HTTP_PROXY variable for the process? */
|
TODO should auto-set the HTTP_PROXY variable for the process? */
|
||||||
flagSet.StringVarP(&options.ProxyURL, "proxy-url", "proxy", "", "URL of the HTTP proxy server"),
|
flagSet.StringVarP(&options.ProxyURL, "proxy-url", "proxy", "", "URL of the HTTP proxy server"),
|
||||||
flagSet.StringVar(&options.ProxySocksURL, "proxy-socks-url", "", "URL of the SOCKS proxy server"),
|
flagSet.StringVar(&options.ProxySocksURL, "proxy-socks-url", "", "URL of the SOCKS proxy server"),
|
||||||
|
flagSet.StringVarP(&options.TraceLogFile, "trace-log", "tlog", "", "file to write sent requests trace log"),
|
||||||
flagSet.StringVar(&options.TraceLogFile, "trace-log", "", "file to write sent requests trace log"),
|
|
||||||
|
|
||||||
flagSet.BoolVar(&options.Version, "version", false, "show nuclei version"),
|
flagSet.BoolVar(&options.Version, "version", false, "show nuclei version"),
|
||||||
|
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
|
||||||
|
flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"),
|
||||||
flagSet.BoolVarP(&options.TemplatesVersion, "templates-version", "tv", false, "shows the version of the installed nuclei-templates"),
|
flagSet.BoolVarP(&options.TemplatesVersion, "templates-version", "tv", false, "shows the version of the installed nuclei-templates"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "update", "Update",
|
createGroup(flagSet, "update", "Update",
|
||||||
flagSet.BoolVar(&options.UpdateNuclei, "update", false, "update nuclei to the latest released version"),
|
flagSet.BoolVar(&options.UpdateNuclei, "update", false, "update nuclei engine to the latest released version"),
|
||||||
flagSet.BoolVarP(&options.UpdateTemplates, "update-templates", "ut", false, "update the community templates to latest released version"),
|
flagSet.BoolVarP(&options.UpdateTemplates, "update-templates", "ut", false, "update nuclei-templates to latest released version"),
|
||||||
flagSet.BoolVarP(&options.NoUpdateTemplates, "no-update-templates", "nut", false, "do not check for nuclei-templates updates"),
|
flagSet.StringVarP(&options.TemplatesDirectory, "update-directory", "ud", templatesDirectory, "overwrite the default directory to install nuclei-templates"),
|
||||||
flagSet.StringVarP(&options.TemplatesDirectory, "update-directory", "ud", templatesDirectory, "overwrite the default nuclei-templates directory"),
|
flagSet.BoolVarP(&options.NoUpdateTemplates, "disable-update-check", "duc", false, "disable automatic nuclei/templates update check"),
|
||||||
)
|
)
|
||||||
|
|
||||||
createGroup(flagSet, "stats", "Statistics",
|
createGroup(flagSet, "stats", "Statistics",
|
||||||
flagSet.BoolVar(&options.EnableProgressBar, "stats", false, "display statistics about the running scan"),
|
flagSet.BoolVar(&options.EnableProgressBar, "stats", false, "display statistics about the running scan"),
|
||||||
flagSet.BoolVar(&options.StatsJSON, "stats-json", false, "write statistics data to an output file in JSONL(ines) format"),
|
flagSet.BoolVarP(&options.StatsJSON, "stats-json", "sj", false, "write statistics data to an output file in JSONL(ines) format"),
|
||||||
flagSet.IntVarP(&options.StatsInterval, "stats-interval", "si", 5, "number of seconds to wait between showing a statistics update"),
|
flagSet.IntVarP(&options.StatsInterval, "stats-interval", "si", 5, "number of seconds to wait between showing a statistics update"),
|
||||||
|
flagSet.BoolVarP(&options.Metrics, "metrics", "m", false, "expose nuclei metrics on a port"),
|
||||||
flagSet.BoolVar(&options.Metrics, "metrics", false, "expose nuclei metrics on a port"),
|
flagSet.IntVarP(&options.MetricsPort, "metrics-port", "mp", 9092, "port to expose nuclei metrics on"),
|
||||||
flagSet.IntVar(&options.MetricsPort, "metrics-port", 9092, "port to expose nuclei metrics on"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_ = flagSet.Parse()
|
_ = flagSet.Parse()
|
||||||
|
|
10
v2/go.mod
10
v2/go.mod
|
@ -31,6 +31,7 @@ require (
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/projectdiscovery/clistats v0.0.8
|
github.com/projectdiscovery/clistats v0.0.8
|
||||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e
|
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e
|
||||||
|
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08
|
||||||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5
|
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5
|
||||||
github.com/projectdiscovery/goflags v0.0.8-0.20211007103353-9b9229e8a240
|
github.com/projectdiscovery/goflags v0.0.8-0.20211007103353-9b9229e8a240
|
||||||
github.com/projectdiscovery/gologger v1.1.4
|
github.com/projectdiscovery/gologger v1.1.4
|
||||||
|
@ -40,7 +41,7 @@ require (
|
||||||
github.com/projectdiscovery/rawhttp v0.0.7
|
github.com/projectdiscovery/rawhttp v0.0.7
|
||||||
github.com/projectdiscovery/retryabledns v1.0.13-0.20210916165024-76c5b76fd59a
|
github.com/projectdiscovery/retryabledns v1.0.13-0.20210916165024-76c5b76fd59a
|
||||||
github.com/projectdiscovery/retryablehttp-go v1.0.2
|
github.com/projectdiscovery/retryablehttp-go v1.0.2
|
||||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9
|
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d
|
||||||
github.com/projectdiscovery/yamldoc-go v1.0.2
|
github.com/projectdiscovery/yamldoc-go v1.0.2
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0
|
github.com/remeh/sizedwaitgroup v1.0.0
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.3.0
|
||||||
|
@ -64,6 +65,7 @@ require (
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
moul.io/http2curl v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -73,6 +75,9 @@ require (
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||||
github.com/andybalholm/cascadia v1.1.0 // indirect
|
github.com/andybalholm/cascadia v1.1.0 // indirect
|
||||||
github.com/antchfx/xpath v1.1.6 // indirect
|
github.com/antchfx/xpath v1.1.6 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||||
|
github.com/bits-and-blooms/bloom/v3 v3.0.1 // indirect
|
||||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
|
@ -86,6 +91,7 @@ require (
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-querystring v1.0.0 // indirect
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.8 // indirect
|
github.com/hashicorp/go-retryablehttp v0.6.8 // indirect
|
||||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||||
|
@ -95,6 +101,7 @@ require (
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
@ -104,6 +111,7 @@ require (
|
||||||
github.com/projectdiscovery/mapcidr v0.0.8 // indirect
|
github.com/projectdiscovery/mapcidr v0.0.8 // indirect
|
||||||
github.com/projectdiscovery/networkpolicy v0.0.1 // indirect
|
github.com/projectdiscovery/networkpolicy v0.0.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.7 // indirect
|
github.com/tklauser/go-sysconf v0.3.7 // indirect
|
||||||
github.com/tklauser/numcpus v0.2.3 // indirect
|
github.com/tklauser/numcpus v0.2.3 // indirect
|
||||||
github.com/trivago/tgo v1.0.7 // indirect
|
github.com/trivago/tgo v1.0.7 // indirect
|
||||||
|
|
25
v2/go.sum
25
v2/go.sum
|
@ -96,16 +96,23 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||||
|
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/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
|
github.com/bits-and-blooms/bloom/v3 v3.0.1 h1:Inlf0YXbgehxVjMPmCGv86iMCKMGPPrPSHtBF5yRHwA=
|
||||||
|
github.com/bits-and-blooms/bloom/v3 v3.0.1/go.mod h1:MC8muvBzzPOFsrcdND/A7kU7kMhkqb9KI70JlZCP+C8=
|
||||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||||
|
@ -318,8 +325,11 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
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/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/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
@ -393,6 +403,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||||
|
@ -468,6 +479,8 @@ github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.
|
||||||
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
|
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
@ -562,7 +575,10 @@ github.com/projectdiscovery/fastdialer v0.0.12/go.mod h1:RkRbxqDCcCFhfNUbkzBIz/i
|
||||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210824195254-0113c1406542/go.mod h1:TuapmLiqtunJOxpM7g0tpTy/TUF/0S+XFyx0B0Wx0DQ=
|
github.com/projectdiscovery/fastdialer v0.0.13-0.20210824195254-0113c1406542/go.mod h1:TuapmLiqtunJOxpM7g0tpTy/TUF/0S+XFyx0B0Wx0DQ=
|
||||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e h1:xMAFYJgRxopAwKrj7HDwMBKJGCGDbHqopS8f959xges=
|
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e h1:xMAFYJgRxopAwKrj7HDwMBKJGCGDbHqopS8f959xges=
|
||||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e/go.mod h1:O1l6+vAQy1QRo9FqyuyJ57W3CwpIXXg7oGo14Le6ZYQ=
|
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e/go.mod h1:O1l6+vAQy1QRo9FqyuyJ57W3CwpIXXg7oGo14Le6ZYQ=
|
||||||
|
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08 h1:NwD1R/du1dqrRKN3SJl9kT6tN3K9puuWFXEvYF2ihew=
|
||||||
|
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08/go.mod h1:paLCnwV8sL7ppqIwVQodQrk3F6mnWafwTDwRd7ywZwQ=
|
||||||
github.com/projectdiscovery/fileutil v0.0.0-20210804142714-ebba15fa53ca/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
github.com/projectdiscovery/fileutil v0.0.0-20210804142714-ebba15fa53ca/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
||||||
|
github.com/projectdiscovery/fileutil v0.0.0-20210914153648-31f843feaad4/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
||||||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5 h1:2dbm7UhrAKnccZttr78CAmG768sSCd+MBn4ayLVDeqA=
|
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5 h1:2dbm7UhrAKnccZttr78CAmG768sSCd+MBn4ayLVDeqA=
|
||||||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
||||||
github.com/projectdiscovery/goflags v0.0.7/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY=
|
github.com/projectdiscovery/goflags v0.0.7/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY=
|
||||||
|
@ -607,8 +623,9 @@ github.com/projectdiscovery/retryablehttp-go v1.0.2 h1:LV1/KAQU+yeWhNVlvveaYFsjB
|
||||||
github.com/projectdiscovery/retryablehttp-go v1.0.2/go.mod h1:dx//aY9V247qHdsRf0vdWHTBZuBQ2vm6Dq5dagxrDYI=
|
github.com/projectdiscovery/retryablehttp-go v1.0.2/go.mod h1:dx//aY9V247qHdsRf0vdWHTBZuBQ2vm6Dq5dagxrDYI=
|
||||||
github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||||
github.com/projectdiscovery/stringsutil v0.0.0-20210823090203-2f5f137e8e1d/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
github.com/projectdiscovery/stringsutil v0.0.0-20210823090203-2f5f137e8e1d/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9 h1:xbL1/7h0k6HE3RzPdYk9W/8pUxESrGWewTaZdIB5Pes=
|
|
||||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||||
|
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d h1:YBYwsm8MrSp9t7mLehyqGwUKZWB08fG+YRePQRo5iFw=
|
||||||
|
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d/go.mod h1:JK4F9ACNPgO+Lbm80khX2q1ABInBMbwIOmbsEE61Sn4=
|
||||||
github.com/projectdiscovery/yamldoc-go v1.0.2 h1:SKb7PHgSOXm27Zci05ba0FxpyQiu6bGEiVMEcjCK1rQ=
|
github.com/projectdiscovery/yamldoc-go v1.0.2 h1:SKb7PHgSOXm27Zci05ba0FxpyQiu6bGEiVMEcjCK1rQ=
|
||||||
github.com/projectdiscovery/yamldoc-go v1.0.2/go.mod h1:7uSxfMXaBmzvw8m5EhOEjB6nhz0rK/H9sUjq1ciZu24=
|
github.com/projectdiscovery/yamldoc-go v1.0.2/go.mod h1:7uSxfMXaBmzvw8m5EhOEjB6nhz0rK/H9sUjq1ciZu24=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
@ -647,6 +664,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
@ -662,8 +681,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
|
||||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
@ -1199,6 +1220,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||||
|
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
|
||||||
|
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
|
||||||
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
|
|
@ -20,6 +20,6 @@ func showBanner() {
|
||||||
gologger.Print().Msgf("%s\n", banner)
|
gologger.Print().Msgf("%s\n", banner)
|
||||||
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
|
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
|
||||||
|
|
||||||
gologger.Error().Label("WRN").Msgf("Use with caution. You are responsible for your actions.\n")
|
gologger.Print().Label("WRN").Msgf("Use with caution. You are responsible for your actions.\n")
|
||||||
gologger.Error().Label("WRN").Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
|
gologger.Print().Label("WRN").Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,6 @@ func ParseOptions(options *types.Options) {
|
||||||
// Check if stdin pipe was given
|
// Check if stdin pipe was given
|
||||||
options.Stdin = hasStdin()
|
options.Stdin = hasStdin()
|
||||||
|
|
||||||
// if VerboseVerbose is set, it implicitly enables the Verbose option as well
|
|
||||||
if options.VerboseVerbose {
|
|
||||||
options.Verbose = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the inputs and configure the logging
|
// Read the inputs and configure the logging
|
||||||
configureOutput(options)
|
configureOutput(options)
|
||||||
|
|
||||||
|
@ -127,7 +122,7 @@ func isValidURL(urlString string) bool {
|
||||||
// configureOutput configures the output logging levels to be displayed on the screen
|
// configureOutput configures the output logging levels to be displayed on the screen
|
||||||
func configureOutput(options *types.Options) {
|
func configureOutput(options *types.Options) {
|
||||||
// If the user desires verbose output, show verbose output
|
// If the user desires verbose output, show verbose output
|
||||||
if options.Verbose || options.VerboseVerbose {
|
if options.Verbose {
|
||||||
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
|
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
|
||||||
}
|
}
|
||||||
if options.Debug {
|
if options.Debug {
|
||||||
|
|
|
@ -7,11 +7,20 @@ import (
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// processSelfContainedTemplates execute a self-contained template.
|
||||||
|
func (r *Runner) processSelfContainedTemplates(template *templates.Template) bool {
|
||||||
|
match, err := template.Executer.Execute("")
|
||||||
|
if err != nil {
|
||||||
|
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", r.colorizer.BrightBlue(template.ID), err)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
// processTemplateWithList execute a template against the list of user provided targets
|
// processTemplateWithList execute a template against the list of user provided targets
|
||||||
func (r *Runner) processTemplateWithList(template *templates.Template) bool {
|
func (r *Runner) processTemplateWithList(template *templates.Template) bool {
|
||||||
results := &atomic.Bool{}
|
results := &atomic.Bool{}
|
||||||
wg := sizedwaitgroup.New(r.options.BulkSize)
|
wg := sizedwaitgroup.New(r.options.BulkSize)
|
||||||
r.hostMap.Scan(func(k, _ []byte) error {
|
processItem := func(k, _ []byte) error {
|
||||||
URL := string(k)
|
URL := string(k)
|
||||||
|
|
||||||
// Skip if the host has had errors
|
// Skip if the host has had errors
|
||||||
|
@ -29,7 +38,13 @@ func (r *Runner) processTemplateWithList(template *templates.Template) bool {
|
||||||
results.CAS(false, match)
|
results.CAS(false, match)
|
||||||
}(URL)
|
}(URL)
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
|
if r.options.Stream {
|
||||||
|
_ = r.hostMapStream.Scan(processItem)
|
||||||
|
} else {
|
||||||
|
r.hostMap.Scan(processItem)
|
||||||
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return results.Load()
|
return results.Load()
|
||||||
}
|
}
|
||||||
|
@ -39,7 +54,7 @@ func (r *Runner) processWorkflowWithList(template *templates.Template) bool {
|
||||||
results := &atomic.Bool{}
|
results := &atomic.Bool{}
|
||||||
wg := sizedwaitgroup.New(r.options.BulkSize)
|
wg := sizedwaitgroup.New(r.options.BulkSize)
|
||||||
|
|
||||||
r.hostMap.Scan(func(k, _ []byte) error {
|
processItem := func(k, _ []byte) error {
|
||||||
URL := string(k)
|
URL := string(k)
|
||||||
|
|
||||||
// Skip if the host has had errors
|
// Skip if the host has had errors
|
||||||
|
@ -53,7 +68,14 @@ func (r *Runner) processWorkflowWithList(template *templates.Template) bool {
|
||||||
results.CAS(false, match)
|
results.CAS(false, match)
|
||||||
}(URL)
|
}(URL)
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if r.options.Stream {
|
||||||
|
_ = r.hostMapStream.Scan(processItem)
|
||||||
|
} else {
|
||||||
|
r.hostMap.Scan(processItem)
|
||||||
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return results.Load()
|
return results.Load()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import (
|
||||||
"go.uber.org/ratelimit"
|
"go.uber.org/ratelimit"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/filekv"
|
||||||
|
"github.com/projectdiscovery/fileutil"
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/hmap/store/hybrid"
|
"github.com/projectdiscovery/hmap/store/hybrid"
|
||||||
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
|
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
|
||||||
|
@ -45,6 +47,7 @@ import (
|
||||||
// Runner is a client for running the enumeration process.
|
// Runner is a client for running the enumeration process.
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
hostMap *hybrid.HybridMap
|
hostMap *hybrid.HybridMap
|
||||||
|
hostMapStream *filekv.FileDB
|
||||||
output output.Writer
|
output output.Writer
|
||||||
interactsh *interactsh.Client
|
interactsh *interactsh.Client
|
||||||
inputCount int64
|
inputCount int64
|
||||||
|
@ -119,6 +122,20 @@ func New(options *types.Options) (*Runner, error) {
|
||||||
}
|
}
|
||||||
runner.hostMap = hm
|
runner.hostMap = hm
|
||||||
|
|
||||||
|
if options.Stream {
|
||||||
|
fkvOptions := filekv.DefaultOptions
|
||||||
|
if tmpFileName, err := fileutil.GetTempFileName(); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not create temporary input file")
|
||||||
|
} else {
|
||||||
|
fkvOptions.Path = tmpFileName
|
||||||
|
}
|
||||||
|
fkv, err := filekv.Open(fkvOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not create temporary unsorted input file")
|
||||||
|
}
|
||||||
|
runner.hostMapStream = fkv
|
||||||
|
}
|
||||||
|
|
||||||
runner.inputCount = 0
|
runner.inputCount = 0
|
||||||
dupeCount := 0
|
dupeCount := 0
|
||||||
|
|
||||||
|
@ -138,6 +155,9 @@ func New(options *types.Options) (*Runner, error) {
|
||||||
runner.inputCount++
|
runner.inputCount++
|
||||||
// nolint:errcheck // ignoring error
|
// nolint:errcheck // ignoring error
|
||||||
runner.hostMap.Set(url, nil)
|
runner.hostMap.Set(url, nil)
|
||||||
|
if options.Stream {
|
||||||
|
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +178,9 @@ func New(options *types.Options) (*Runner, error) {
|
||||||
runner.inputCount++
|
runner.inputCount++
|
||||||
// nolint:errcheck // ignoring error
|
// nolint:errcheck // ignoring error
|
||||||
runner.hostMap.Set(url, nil)
|
runner.hostMap.Set(url, nil)
|
||||||
|
if options.Stream {
|
||||||
|
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +203,9 @@ func New(options *types.Options) (*Runner, error) {
|
||||||
runner.inputCount++
|
runner.inputCount++
|
||||||
// nolint:errcheck // ignoring error
|
// nolint:errcheck // ignoring error
|
||||||
runner.hostMap.Set(url, nil)
|
runner.hostMap.Set(url, nil)
|
||||||
|
if options.Stream {
|
||||||
|
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input.Close()
|
input.Close()
|
||||||
}
|
}
|
||||||
|
@ -290,6 +316,9 @@ func (r *Runner) Close() {
|
||||||
if r.projectFile != nil {
|
if r.projectFile != nil {
|
||||||
r.projectFile.Close()
|
r.projectFile.Close()
|
||||||
}
|
}
|
||||||
|
if r.options.Stream {
|
||||||
|
r.hostMapStream.Close()
|
||||||
|
}
|
||||||
protocolinit.Close()
|
protocolinit.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +540,9 @@ func (r *Runner) RunEnumeration() error {
|
||||||
go func(template *templates.Template) {
|
go func(template *templates.Template) {
|
||||||
defer wgtemplates.Done()
|
defer wgtemplates.Done()
|
||||||
|
|
||||||
if len(template.Workflows) > 0 {
|
if template.SelfContained {
|
||||||
|
results.CAS(false, r.processSelfContainedTemplates(template))
|
||||||
|
} else if len(template.Workflows) > 0 {
|
||||||
results.CAS(false, r.processWorkflowWithList(template))
|
results.CAS(false, r.processWorkflowWithList(template))
|
||||||
} else {
|
} else {
|
||||||
results.CAS(false, r.processTemplateWithList(template))
|
results.CAS(false, r.processTemplateWithList(template))
|
||||||
|
|
|
@ -92,10 +92,14 @@ type TCPServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTCPServer creates a new TCP server from a handler
|
// NewTCPServer creates a new TCP server from a handler
|
||||||
func NewTCPServer(handler func(conn net.Conn)) *TCPServer {
|
func NewTCPServer(handler func(conn net.Conn), port ...int) *TCPServer {
|
||||||
server := &TCPServer{}
|
server := &TCPServer{}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
var gotPort int
|
||||||
|
if len(port) > 0 {
|
||||||
|
gotPort = port[0]
|
||||||
|
}
|
||||||
|
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", gotPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ type Config struct {
|
||||||
const nucleiConfigFilename = ".templates-config.json"
|
const nucleiConfigFilename = ".templates-config.json"
|
||||||
|
|
||||||
// Version is the current version of nuclei
|
// Version is the current version of nuclei
|
||||||
const Version = `2.5.3-dev`
|
const Version = `2.5.4-dev`
|
||||||
|
|
||||||
func getConfigDetails() (string, error) {
|
func getConfigDetails() (string, error) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
|
|
|
@ -224,6 +224,15 @@ var functions = map[string]govaluate.ExpressionFunction{
|
||||||
}
|
}
|
||||||
return rand.Intn(max-min) + min, nil
|
return rand.Intn(max-min) + min, nil
|
||||||
},
|
},
|
||||||
|
"unixtime": func(args ...interface{}) (interface{}, error) {
|
||||||
|
seconds := 0
|
||||||
|
if len(args) >= 1 {
|
||||||
|
seconds = int(args[0].(float64))
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
offset := now.Add(time.Duration(seconds) * time.Second)
|
||||||
|
return offset.Unix(), nil
|
||||||
|
},
|
||||||
// Time Functions
|
// Time Functions
|
||||||
"waitfor": func(args ...interface{}) (interface{}, error) {
|
"waitfor": func(args ...interface{}) (interface{}, error) {
|
||||||
seconds := args[0].(float64)
|
seconds := args[0].(float64)
|
||||||
|
|
|
@ -59,15 +59,15 @@ type InternalWrappedEvent struct {
|
||||||
// ResultEvent is a wrapped result event for a single nuclei output.
|
// ResultEvent is a wrapped result event for a single nuclei output.
|
||||||
type ResultEvent struct {
|
type ResultEvent struct {
|
||||||
// TemplateID is the ID of the template for the result.
|
// TemplateID is the ID of the template for the result.
|
||||||
TemplateID string `json:"templateID"`
|
TemplateID string `json:"template-id"`
|
||||||
// TemplatePath is the path of template
|
// TemplatePath is the path of template
|
||||||
TemplatePath string `json:"-"`
|
TemplatePath string `json:"-"`
|
||||||
// Info contains information block of the template for the result.
|
// Info contains information block of the template for the result.
|
||||||
Info model.Info `json:"info,inline"`
|
Info model.Info `json:"info,inline"`
|
||||||
// MatcherName is the name of the matcher matched if any.
|
// MatcherName is the name of the matcher matched if any.
|
||||||
MatcherName string `json:"matcher_name,omitempty"`
|
MatcherName string `json:"matcher-name,omitempty"`
|
||||||
// ExtractorName is the name of the extractor matched if any.
|
// ExtractorName is the name of the extractor matched if any.
|
||||||
ExtractorName string `json:"extractor_name,omitempty"`
|
ExtractorName string `json:"extractor-name,omitempty"`
|
||||||
// Type is the type of the result event.
|
// Type is the type of the result event.
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
// Host is the host input on which match was found.
|
// Host is the host input on which match was found.
|
||||||
|
@ -75,9 +75,9 @@ type ResultEvent struct {
|
||||||
// Path is the path input on which match was found.
|
// Path is the path input on which match was found.
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
// Matched contains the matched input in its transformed form.
|
// Matched contains the matched input in its transformed form.
|
||||||
Matched string `json:"matched,omitempty"`
|
Matched string `json:"matched-at,omitempty"`
|
||||||
// ExtractedResults contains the extraction result from the inputs.
|
// ExtractedResults contains the extraction result from the inputs.
|
||||||
ExtractedResults []string `json:"extracted_results,omitempty"`
|
ExtractedResults []string `json:"extracted-results,omitempty"`
|
||||||
// Request is the optional, dumped request for the match.
|
// Request is the optional, dumped request for the match.
|
||||||
Request string `json:"request,omitempty"`
|
Request string `json:"request,omitempty"`
|
||||||
// Response is the optional, dumped response for the match.
|
// Response is the optional, dumped response for the match.
|
||||||
|
@ -90,7 +90,9 @@ type ResultEvent struct {
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
// Interaction is the full details of interactsh interaction.
|
// Interaction is the full details of interactsh interaction.
|
||||||
Interaction *server.Interaction `json:"interaction,omitempty"`
|
Interaction *server.Interaction `json:"interaction,omitempty"`
|
||||||
|
// CURLCommand is an optional curl command to reproduce the request
|
||||||
|
// Only applicable if the report is for HTTP.
|
||||||
|
CURLCommand string `json:"curl-command,omitempty"`
|
||||||
FileToIndexPosition map[string]int `json:"-"`
|
FileToIndexPosition map[string]int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
@ -17,7 +18,10 @@ import (
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/utils/stats"
|
"github.com/projectdiscovery/nuclei/v2/pkg/utils/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mandatoryFieldMissingTemplate = "mandatory '%s' field is missing"
|
const (
|
||||||
|
mandatoryFieldMissingTemplate = "mandatory '%s' field is missing"
|
||||||
|
invalidFieldFormatTemplate = "invalid field format for '%s' (allowed format is %s)"
|
||||||
|
)
|
||||||
|
|
||||||
// LoadTemplate returns true if the template is valid and matches the filtering criteria.
|
// LoadTemplate returns true if the template is valid and matches the filtering criteria.
|
||||||
func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags []string) (bool, error) {
|
func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags []string) (bool, error) {
|
||||||
|
@ -30,12 +34,12 @@ func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags []
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
templateInfo := template.Info
|
if validationError := validateTemplateFields(template); validationError != nil {
|
||||||
if validationError := validateMandatoryInfoFields(&templateInfo); validationError != nil {
|
stats.Increment(SyntaxErrorStats)
|
||||||
return false, validationError
|
return false, validationError
|
||||||
}
|
}
|
||||||
|
|
||||||
return isTemplateInfoMetadataMatch(tagFilter, &templateInfo, extraTags)
|
return isTemplateInfoMetadataMatch(tagFilter, &template.Info, extraTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWorkflow returns true if the workflow is valid and matches the filtering criteria.
|
// LoadWorkflow returns true if the workflow is valid and matches the filtering criteria.
|
||||||
|
@ -45,10 +49,8 @@ func LoadWorkflow(templatePath string) (bool, error) {
|
||||||
return false, templateParseError
|
return false, templateParseError
|
||||||
}
|
}
|
||||||
|
|
||||||
templateInfo := template.Info
|
|
||||||
|
|
||||||
if len(template.Workflows) > 0 {
|
if len(template.Workflows) > 0 {
|
||||||
if validationError := validateMandatoryInfoFields(&templateInfo); validationError != nil {
|
if validationError := validateTemplateFields(template); validationError != nil {
|
||||||
return false, validationError
|
return false, validationError
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -71,18 +73,29 @@ func isTemplateInfoMetadataMatch(tagFilter *filter.TagFilter, templateInfo *mode
|
||||||
return match, err
|
return match, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateMandatoryInfoFields(info *model.Info) error {
|
func validateTemplateFields(template *templates.Template) error {
|
||||||
if info == nil {
|
info := template.Info
|
||||||
return fmt.Errorf(mandatoryFieldMissingTemplate, "info")
|
|
||||||
}
|
var errors []string
|
||||||
|
|
||||||
if utils.IsBlank(info.Name) {
|
if utils.IsBlank(info.Name) {
|
||||||
return fmt.Errorf(mandatoryFieldMissingTemplate, "name")
|
errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Authors.IsEmpty() {
|
if info.Authors.IsEmpty() {
|
||||||
return fmt.Errorf(mandatoryFieldMissingTemplate, "author")
|
errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "author"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if template.ID == "" {
|
||||||
|
errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "id"))
|
||||||
|
} else if !templateIDRegexp.MatchString(template.ID) {
|
||||||
|
errors = append(errors, fmt.Sprintf(invalidFieldFormatTemplate, "id", templateIDRegexp.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(errors, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +103,7 @@ var (
|
||||||
parsedTemplatesCache *cache.Templates
|
parsedTemplatesCache *cache.Templates
|
||||||
ShouldValidate bool
|
ShouldValidate bool
|
||||||
fieldErrorRegexp = regexp.MustCompile(`not found in`)
|
fieldErrorRegexp = regexp.MustCompile(`not found in`)
|
||||||
|
templateIDRegexp = regexp.MustCompile(`^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter"
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/stringslice"
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadTemplate(t *testing.T) {
|
||||||
|
origTemplatesCache := parsedTemplatesCache
|
||||||
|
defer func() { parsedTemplatesCache = origTemplatesCache }()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
template *templates.Template
|
||||||
|
templateErr error
|
||||||
|
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
template: &templates.Template{
|
||||||
|
ID: "CVE-2021-27330",
|
||||||
|
Info: model.Info{
|
||||||
|
Name: "Valid template",
|
||||||
|
Authors: stringslice.StringSlice{Value: "Author"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyTemplate",
|
||||||
|
template: &templates.Template{},
|
||||||
|
expectedErr: errors.New("mandatory 'name' field is missing, mandatory 'author' field is missing, mandatory 'id' field is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyNameWithInvalidID",
|
||||||
|
template: &templates.Template{
|
||||||
|
ID: "invalid id",
|
||||||
|
Info: model.Info{
|
||||||
|
Authors: stringslice.StringSlice{Value: "Author"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: errors.New("mandatory 'name' field is missing, invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
parsedTemplatesCache.Store(tc.name, tc.template, tc.templateErr)
|
||||||
|
|
||||||
|
tagFilter := filter.New(&filter.Config{})
|
||||||
|
success, err := LoadTemplate(tc.name, tagFilter, nil)
|
||||||
|
if tc.expectedErr == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, success)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, tc.expectedErr, err)
|
||||||
|
require.False(t, success)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("invalidTemplateID", func(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
id string
|
||||||
|
success bool
|
||||||
|
}{
|
||||||
|
{id: "A-B-C", success: true},
|
||||||
|
{id: "A-B-C-1", success: true},
|
||||||
|
{id: "CVE_2021_27330", success: true},
|
||||||
|
{id: "ABC DEF", success: false},
|
||||||
|
{id: "_-__AAA_", success: false},
|
||||||
|
{id: " CVE-2021-27330", success: false},
|
||||||
|
{id: "CVE-2021-27330 ", success: false},
|
||||||
|
{id: "CVE-2021-27330-", success: false},
|
||||||
|
{id: "-CVE-2021-27330-", success: false},
|
||||||
|
{id: "CVE-2021--27330", success: false},
|
||||||
|
{id: "CVE-2021+27330", success: false},
|
||||||
|
}
|
||||||
|
for i, tc := range tt {
|
||||||
|
name := fmt.Sprintf("regexp%d", i)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
template := &templates.Template{
|
||||||
|
ID: tc.id,
|
||||||
|
Info: model.Info{
|
||||||
|
Name: "Valid template",
|
||||||
|
Authors: stringslice.StringSlice{Value: "Author"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
parsedTemplatesCache.Store(name, template, nil)
|
||||||
|
|
||||||
|
tagFilter := filter.New(&filter.Config{})
|
||||||
|
success, err := LoadTemplate(name, tagFilter, nil)
|
||||||
|
if tc.success {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, success)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, errors.New("invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)"), err)
|
||||||
|
require.False(t, success)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ type Generator struct {
|
||||||
type Type int
|
type Type int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Sniper replaces one iteration of the payload with a value.
|
// Batteringram replaces same payload into all of the defined payload positions at once.
|
||||||
BatteringRam Type = iota + 1
|
BatteringRam Type = iota + 1
|
||||||
// PitchFork replaces variables with positional value from multiple wordlists
|
// PitchFork replaces variables with positional value from multiple wordlists
|
||||||
PitchFork
|
PitchFork
|
||||||
|
@ -43,10 +43,10 @@ func New(payloads map[string]interface{}, payloadType Type, templatePath string)
|
||||||
generator.Type = payloadType
|
generator.Type = payloadType
|
||||||
generator.payloads = compiled
|
generator.payloads = compiled
|
||||||
|
|
||||||
// Validate the sniper/batteringram payload set
|
// Validate the batteringram payload set
|
||||||
if payloadType == BatteringRam {
|
if payloadType == BatteringRam {
|
||||||
if len(payloads) != 1 {
|
if len(payloads) != 1 {
|
||||||
return nil, errors.New("sniper/batteringram must have single payload set")
|
return nil, errors.New("batteringram must have single payload set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return generator, nil
|
return generator, nil
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package generators
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildPayloadFromOptions returns a map with the payloads provided via CLI
|
||||||
|
func BuildPayloadFromOptions(options *types.Options) map[string]interface{} {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
// merge with vars
|
||||||
|
if !options.Vars.IsEmpty() {
|
||||||
|
m = MergeMaps(m, options.Vars.AsMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge with env vars
|
||||||
|
if options.EnvironmentVariables {
|
||||||
|
m = MergeMaps(EnvVars(), m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -1,12 +1,18 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newhttpClient creates a new http client for headless communication with a timeout
|
// newhttpClient creates a new http client for headless communication with a timeout
|
||||||
|
@ -22,5 +28,40 @@ func newhttpClient(options *types.Options) *http.Client {
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return &http.Client{Transport: transport, Timeout: time.Duration(options.Timeout*3) * time.Second}
|
|
||||||
|
if options.ProxyURL != "" {
|
||||||
|
if proxyURL, err := url.Parse(options.ProxyURL); err == nil {
|
||||||
|
transport.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
}
|
||||||
|
} else if options.ProxySocksURL != "" {
|
||||||
|
var proxyAuth *proxy.Auth
|
||||||
|
|
||||||
|
socksURL, proxyErr := url.Parse(options.ProxySocksURL)
|
||||||
|
if proxyErr == nil {
|
||||||
|
proxyAuth = &proxy.Auth{}
|
||||||
|
proxyAuth.User = socksURL.User.Username()
|
||||||
|
proxyAuth.Password, _ = socksURL.User.Password()
|
||||||
|
}
|
||||||
|
dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct)
|
||||||
|
dc := dialer.(interface {
|
||||||
|
DialContext(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
})
|
||||||
|
if proxyErr == nil {
|
||||||
|
transport.DialContext = dc.DialContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
|
||||||
|
httpclient := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: time.Duration(options.Timeout*3) * time.Second,
|
||||||
|
Jar: jar,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
// the browser should follow redirects not us
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpclient
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/go-rod/rod/lib/proto"
|
"github.com/go-rod/rod/lib/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Page is a single page in an isolated browser instanace
|
// Page is a single page in an isolated browser instance
|
||||||
type Page struct {
|
type Page struct {
|
||||||
page *rod.Page
|
page *rod.Page
|
||||||
rules []requestRule
|
rules []requestRule
|
||||||
|
|
|
@ -2,9 +2,11 @@ package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -16,18 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActionNavigate(t *testing.T) {
|
func TestActionNavigate(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
response := `
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Nuclei Test Page</title>
|
<title>Nuclei Test Page</title>
|
||||||
|
@ -35,270 +26,429 @@ func TestActionNavigate(t *testing.T) {
|
||||||
<body>
|
<body>
|
||||||
<h1>Nuclei Test</h1>
|
<h1>Nuclei Test</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>`)
|
</html>`
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}}, {ActionType: "waitload"}}
|
actions := []*Action{{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}}, {ActionType: "waitload"}}
|
||||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
defer page.Close()
|
|
||||||
|
|
||||||
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionScript(t *testing.T) {
|
func TestActionScript(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>Nuclei Test Page</body>
|
||||||
|
<script>window.test = 'some-data';</script>
|
||||||
|
</html>`
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
timeout := 2 * time.Second
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
|
|
||||||
t.Run("run-and-results", func(t *testing.T) {
|
t.Run("run-and-results", func(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Nuclei Test Page</title>
|
|
||||||
</head>
|
|
||||||
<body>Nuclei Test Page</body>
|
|
||||||
<script>window.test = 'some-data';</script>
|
|
||||||
</html>`)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
{ActionType: "script", Name: "test", Data: map[string]string{"code": "window.test"}},
|
{ActionType: "script", Name: "test", Data: map[string]string{"code": "window.test"}},
|
||||||
}
|
}
|
||||||
out, page, err := instance.Run(parsed, actions, 20*time.Second)
|
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out map[string]string) {
|
||||||
require.Nil(t, err, "could not run page actions")
|
require.Nil(t, err, "could not run page actions")
|
||||||
defer page.Close()
|
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")
|
||||||
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")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("hook", func(t *testing.T) {
|
t.Run("hook", func(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Nuclei Test Page</title>
|
|
||||||
</head>
|
|
||||||
<body>Nuclei Test Page</body>
|
|
||||||
</html>`)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "script", Data: map[string]string{"code": "window.test = 'some-data';", "hook": "true"}},
|
{ActionType: "script", Data: map[string]string{"code": "window.test = 'some-data';", "hook": "true"}},
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
{ActionType: "script", Name: "test", Data: map[string]string{"code": "window.test"}},
|
{ActionType: "script", Name: "test", Data: map[string]string{"code": "window.test"}},
|
||||||
}
|
}
|
||||||
out, page, err := instance.Run(parsed, actions, 20*time.Second)
|
testHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out map[string]string) {
|
||||||
require.Nil(t, err, "could not run page actions")
|
require.Nil(t, err, "could not run page actions")
|
||||||
defer page.Close()
|
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")
|
||||||
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")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionClick(t *testing.T) {
|
func TestActionClick(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
response := `
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Nuclei Test Page</title>
|
<title>Nuclei Test Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>Nuclei Test Page</body>
|
<body>Nuclei Test Page</body>
|
||||||
<button onclick='this.setAttribute("a", "ok")'>click me</button>
|
<button onclick='this.setAttribute("a", "ok")'>click me</button>
|
||||||
</html>`)
|
</html>`
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
{ActionType: "click", Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
|
{ActionType: "click", Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
|
||||||
}
|
}
|
||||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
defer page.Close()
|
|
||||||
|
|
||||||
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
el := page.Page().MustElement("button")
|
require.Nil(t, err, "could not run page actions")
|
||||||
val := el.MustAttribute("a")
|
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
||||||
require.Equal(t, "ok", *val, "could not click button")
|
el := page.Page().MustElement("button")
|
||||||
|
val := el.MustAttribute("a")
|
||||||
|
require.Equal(t, "ok", *val, "could not click button")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionRightClick(t *testing.T) {
|
func TestActionRightClick(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
response := `
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Nuclei Test Page</title>
|
<title>Nuclei Test Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>Nuclei Test Page</body>
|
<body>Nuclei Test Page</body>
|
||||||
<button id="test" onrightclick=''>click me</button>
|
<button id="test" onrightclick=''>click me</button>
|
||||||
<script>
|
<script>
|
||||||
elm = document.getElementById("test");
|
elm = document.getElementById("test");
|
||||||
elm.onmousedown = function(event) {
|
elm.onmousedown = function(event) {
|
||||||
if (event.which == 3) {
|
if (event.which == 3) {
|
||||||
elm.setAttribute("a", "ok")
|
elm.setAttribute("a", "ok")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>`)
|
</html>`
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
{ActionType: "rightclick", Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
|
{ActionType: "rightclick", Data: map[string]string{"selector": "button"}}, // Use css selector for clicking
|
||||||
}
|
}
|
||||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
defer page.Close()
|
|
||||||
|
|
||||||
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
el := page.Page().MustElement("button")
|
require.Nil(t, err, "could not run page actions")
|
||||||
val := el.MustAttribute("a")
|
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
||||||
require.Equal(t, "ok", *val, "could not click button")
|
el := page.Page().MustElement("button")
|
||||||
|
val := el.MustAttribute("a")
|
||||||
|
require.Equal(t, "ok", *val, "could not click button")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionTextInput(t *testing.T) {
|
func TestActionTextInput(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
response := `
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Nuclei Test Page</title>
|
<title>Nuclei Test Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>Nuclei Test Page</body>
|
<body>Nuclei Test Page</body>
|
||||||
<input type="text" onchange="this.setAttribute('event', 'input-change')">
|
<input type="text" onchange="this.setAttribute('event', 'input-change')">
|
||||||
</html>`)
|
</html>`
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
{ActionType: "text", Data: map[string]string{"selector": "input", "value": "test"}},
|
{ActionType: "text", Data: map[string]string{"selector": "input", "value": "test"}},
|
||||||
}
|
}
|
||||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
defer page.Close()
|
|
||||||
|
|
||||||
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
el := page.Page().MustElement("input")
|
require.Nil(t, err, "could not run page actions")
|
||||||
val := el.MustAttribute("event")
|
require.Equal(t, "Nuclei Test Page", page.Page().MustInfo().Title, "could not navigate correctly")
|
||||||
require.Equal(t, "input-change", *val, "could not get input change")
|
el := page.Page().MustElement("input")
|
||||||
require.Equal(t, "test", el.MustText(), "could not get input change value")
|
val := el.MustAttribute("event")
|
||||||
|
require.Equal(t, "input-change", *val, "could not get input change")
|
||||||
|
require.Equal(t, "test", el.MustText(), "could not get input change value")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionHeadersChange(t *testing.T) {
|
func TestActionHeadersChange(t *testing.T) {
|
||||||
_ = protocolstate.Init(&types.Options{})
|
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
|
||||||
require.Nil(t, err, "could not create browser")
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
|
||||||
require.Nil(t, err, "could not create browser instance")
|
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Header.Get("Test") == "Hello" {
|
|
||||||
fmt.Fprintln(w, `found`)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
|
||||||
require.Nil(t, err, "could not parse URL")
|
|
||||||
|
|
||||||
actions := []*Action{
|
actions := []*Action{
|
||||||
{ActionType: "setheader", Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},
|
{ActionType: "setheader", Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
{ActionType: "waitload"},
|
{ActionType: "waitload"},
|
||||||
}
|
}
|
||||||
_, page, err := instance.Run(parsed, actions, 20*time.Second)
|
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
defer page.Close()
|
|
||||||
|
|
||||||
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
if r.Header.Get("Test") == "Hello" {
|
||||||
|
_, _ = fmt.Fprintln(w, `found`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestActionWaitVisible(t *testing.T) {
|
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
|
||||||
t.Run("wait for an element being visible", func(t *testing.T) {
|
require.Nil(t, err, "could not run page actions")
|
||||||
testWaitVisible(t, 2*time.Second, func(page *Page, err error) {
|
require.Equal(t, "found", strings.ToLower(strings.TrimSpace(page.Page().MustElement("html").MustText())), "could not set header correctly")
|
||||||
require.Nil(t, err, "could not run page actions")
|
|
||||||
|
|
||||||
page.Page().MustElement("button").MustVisible()
|
|
||||||
page.Close()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("timeout because of element not visible", func(t *testing.T) {
|
|
||||||
testWaitVisible(t, time.Second/2, func(page *Page, err error) {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "Element did not appear in the given amount of time")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWaitVisible(t *testing.T, timeout time.Duration, assert func(page *Page, err error)) {
|
func TestActionScreenshot(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>Nuclei Test Page</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
{ActionType: "screenshot", Data: map[string]string{"to": "test"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
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()
|
||||||
|
require.FileExists(t, "test.png", el, "could not get screenshot file")
|
||||||
|
_ = os.Remove("test.png")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionTimeInput(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>Nuclei Test Page</body>
|
||||||
|
<input type="date">
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
{ActionType: "time", 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) {
|
||||||
|
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")
|
||||||
|
require.Equal(t, "2006-01-02", el.MustText(), "could not get input time value")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionSelectInput(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<select name="test" id="test">
|
||||||
|
<option value="test1">Test1</option>
|
||||||
|
<option value="test2">Test2</option>
|
||||||
|
</select>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
{ActionType: "select", 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) {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionFilesInput(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>Nuclei Test Page</body>
|
||||||
|
<input type="file">
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
{ActionType: "files", 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) {
|
||||||
|
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")
|
||||||
|
require.Equal(t, "C:\\fakepath\\test1.pdf", el.MustText(), "could not get input file")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionWaitLoad(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<button id="test">Wait for me!</button>
|
||||||
|
<script>
|
||||||
|
window.onload = () => document.querySelector('#test').style.color = 'red';
|
||||||
|
</script>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
el := page.Page().MustElement("button")
|
||||||
|
style, attributeErr := el.Attribute("style")
|
||||||
|
require.Nil(t, attributeErr)
|
||||||
|
require.Equal(t, "color: red;", *style, "could not get color")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionGetResource(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img id="test" src="https://nuclei.projectdiscovery.io/static/logo.png">
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "getresource", 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) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
require.Equal(t, len(out["src"]), 3159, "could not find resource")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionExtract(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<button id="test">Wait for me!</button>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "extract", 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) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
require.Equal(t, "Wait for me!", out["extract"], "could not extract text")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionSetMethod(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "setmethod", Data: map[string]string{"part": "x", "method": "SET"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
require.Equal(t, "SET", page.rules[0].Args["method"], "could not find resource")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionAddHeader(t *testing.T) {
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "addheader", Data: map[string]string{"part": "request", "key": "Test", "value": "Hello"}},
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Test") == "Hello" {
|
||||||
|
_, _ = fmt.Fprintln(w, `found`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionDeleteHeader(t *testing.T) {
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "addheader", Data: map[string]string{"part": "request", "key": "Test1", "value": "Hello"}},
|
||||||
|
{ActionType: "addheader", Data: map[string]string{"part": "request", "key": "Test2", "value": "World"}},
|
||||||
|
{ActionType: "deleteheader", Data: map[string]string{"part": "request", "key": "Test2"}},
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Test1") == "Hello" && r.Header.Get("Test2") == "" {
|
||||||
|
_, _ = fmt.Fprintln(w, `header deleted`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionSetBody(t *testing.T) {
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "setbody", Data: map[string]string{"part": "request", "body": "hello"}},
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
_, _ = fmt.Fprintln(w, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out map[string]string) {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionKeyboard(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type="text" name="test" id="test">
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitload"},
|
||||||
|
{ActionType: "click", Data: map[string]string{"selector": "input"}},
|
||||||
|
{ActionType: "keyboard", Data: map[string]string{"keys": "Test2"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionSleep(t *testing.T) {
|
||||||
response := `
|
response := `
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -310,6 +460,59 @@ func testWaitVisible(t *testing.T, timeout time.Duration, assert func(page *Page
|
||||||
</script>
|
</script>
|
||||||
</html>`
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "sleep", Data: map[string]string{"duration": "2"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out map[string]string) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
require.True(t, page.Page().MustElement("button").MustVisible(), "could not get button")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionWaitVisible(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nuclei Test Page</title>
|
||||||
|
</head>
|
||||||
|
<button style="display:none" id="test">Wait for me!</button>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => document.querySelector('#test').style.display = '', 1000);
|
||||||
|
</script>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
actions := []*Action{
|
||||||
|
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
||||||
|
{ActionType: "waitvisible", Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
require.Nil(t, err, "could not run page actions")
|
||||||
|
|
||||||
|
page.Page().MustElement("button").MustVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("timeout because of element not visible", func(t *testing.T) {
|
||||||
|
testHeadlessSimpleResponse(t, response, actions, time.Second/2, func(page *Page, err error, out map[string]string) {
|
||||||
|
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)) {
|
||||||
|
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)) {
|
||||||
|
t.Helper()
|
||||||
_ = protocolstate.Init(&types.Options{})
|
_ = protocolstate.Init(&types.Options{})
|
||||||
|
|
||||||
browser, err := New(&types.Options{ShowBrowser: false})
|
browser, err := New(&types.Options{ShowBrowser: false})
|
||||||
|
@ -318,19 +521,17 @@ func testWaitVisible(t *testing.T, timeout time.Duration, assert func(page *Page
|
||||||
|
|
||||||
instance, err := browser.NewInstance()
|
instance, err := browser.NewInstance()
|
||||||
require.Nil(t, err, "could not create browser instance")
|
require.Nil(t, err, "could not create browser instance")
|
||||||
|
defer instance.Close()
|
||||||
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
fmt.Fprintln(w, response)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
parsed, err := url.Parse(ts.URL)
|
parsed, err := url.Parse(ts.URL)
|
||||||
require.Nil(t, err, "could not parse URL")
|
require.Nil(t, err, "could not parse URL")
|
||||||
|
extractedData, page, err := instance.Run(parsed, actions, timeout)
|
||||||
|
assert(page, err, extractedData)
|
||||||
|
|
||||||
actions := []*Action{
|
if page != nil {
|
||||||
{ActionType: "navigate", Data: map[string]string{"url": "{{BaseURL}}"}},
|
page.Close()
|
||||||
{ActionType: "waitvisible", Data: map[string]string{"by": "x", "xpath": "//button[@id='test']"}},
|
|
||||||
}
|
}
|
||||||
_, page, err := instance.Run(parsed, actions, timeout)
|
|
||||||
assert(page, err)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ import (
|
||||||
|
|
||||||
// routingRuleHandler handles proxy rule for actions related to request/response modification
|
// routingRuleHandler handles proxy rule for actions related to request/response modification
|
||||||
func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
|
func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
|
||||||
|
// usually browsers don't use chunked transfer encoding so we set the content-length nevertheless
|
||||||
|
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
|
||||||
|
|
||||||
for _, rule := range p.rules {
|
for _, rule := range p.rules {
|
||||||
if rule.Part != "request" {
|
if rule.Part != "request" {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -20,6 +21,7 @@ import (
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/race"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/race"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/raw"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/raw"
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||||
"github.com/projectdiscovery/rawhttp"
|
"github.com/projectdiscovery/rawhttp"
|
||||||
"github.com/projectdiscovery/retryablehttp-go"
|
"github.com/projectdiscovery/retryablehttp-go"
|
||||||
)
|
)
|
||||||
|
@ -38,9 +40,22 @@ type generatedRequest struct {
|
||||||
dynamicValues map[string]interface{}
|
dynamicValues map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *generatedRequest) URL() string {
|
||||||
|
if g.request != nil {
|
||||||
|
return g.request.URL.String()
|
||||||
|
}
|
||||||
|
if g.rawRequest != nil {
|
||||||
|
return g.rawRequest.FullURL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Make creates a http request for the provided input.
|
// Make creates a http request for the provided input.
|
||||||
// It returns io.EOF as error when all the requests have been exhausted.
|
// It returns io.EOF as error when all the requests have been exhausted.
|
||||||
func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) {
|
func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) {
|
||||||
|
if r.request.SelfContained {
|
||||||
|
return r.makeSelfContainedRequest(dynamicValues, interactURL)
|
||||||
|
}
|
||||||
// We get the next payload for the request.
|
// We get the next payload for the request.
|
||||||
data, payloads, ok := r.nextValue()
|
data, payloads, ok := r.nextValue()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -48,6 +63,14 @@ func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interfa
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if interactURL != "" {
|
||||||
|
data = r.options.Interactsh.ReplaceMarkers(data, interactURL)
|
||||||
|
|
||||||
|
for payloadName, payloadValue := range payloads {
|
||||||
|
payloads[payloadName] = r.options.Interactsh.ReplaceMarkers(types.ToString(payloadValue), interactURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parsed, err := url.Parse(baseURL)
|
parsed, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -55,22 +78,22 @@ func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interfa
|
||||||
|
|
||||||
data, parsed = baseURLWithTemplatePrefs(data, parsed)
|
data, parsed = baseURLWithTemplatePrefs(data, parsed)
|
||||||
|
|
||||||
trailingSlash := false
|
|
||||||
isRawRequest := len(r.request.Raw) > 0
|
isRawRequest := len(r.request.Raw) > 0
|
||||||
|
|
||||||
|
// If the request is not a raw request, and the URL input path is suffixed with
|
||||||
|
// a trailing slash, and our Input URL is also suffixed with a trailing slash,
|
||||||
|
// mark trailingSlash bool as true which will be later used during variable generation
|
||||||
|
// to generate correct path removed slash which would otherwise generate // invalid sequence.
|
||||||
|
// TODO: Figure out a cleaner way to do this sanitization.
|
||||||
|
trailingSlash := false
|
||||||
if !isRawRequest && strings.HasSuffix(parsed.Path, "/") && strings.Contains(data, "{{BaseURL}}/") {
|
if !isRawRequest && strings.HasSuffix(parsed.Path, "/") && strings.Contains(data, "{{BaseURL}}/") {
|
||||||
trailingSlash = true
|
trailingSlash = true
|
||||||
}
|
}
|
||||||
values := generators.MergeMaps(dynamicValues, generateVariables(parsed, trailingSlash))
|
|
||||||
|
|
||||||
// merge with vars
|
values := generators.MergeMaps(
|
||||||
if !r.options.Options.Vars.IsEmpty() {
|
generators.MergeMaps(dynamicValues, generateVariables(parsed, trailingSlash)),
|
||||||
values = generators.MergeMaps(values, r.options.Options.Vars.AsMap())
|
generators.BuildPayloadFromOptions(r.request.options.Options),
|
||||||
}
|
)
|
||||||
|
|
||||||
// merge with env vars
|
|
||||||
if r.options.Options.EnvironmentVariables {
|
|
||||||
values = generators.MergeMaps(generators.EnvVars(), values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If data contains \n it's a raw request, process it like raw. Else
|
// If data contains \n it's a raw request, process it like raw. Else
|
||||||
// continue with the template based request flow.
|
// continue with the template based request flow.
|
||||||
|
@ -80,6 +103,48 @@ func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interfa
|
||||||
return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL)
|
return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *requestGenerator) makeSelfContainedRequest(dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) {
|
||||||
|
// We get the next payload for the request.
|
||||||
|
data, payloads, ok := r.nextValue()
|
||||||
|
if !ok {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
isRawRequest := r.request.isRaw()
|
||||||
|
|
||||||
|
// If the request is a raw request, get the URL from the request
|
||||||
|
// header and use it to make the request.
|
||||||
|
if isRawRequest {
|
||||||
|
// Get the hostname from the URL section to build the request.
|
||||||
|
reader := bufio.NewReader(strings.NewReader(data))
|
||||||
|
s, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(s, " ")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil, fmt.Errorf("malformed request supplied")
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse request URL: %s", err)
|
||||||
|
}
|
||||||
|
values := generators.MergeMaps(
|
||||||
|
generators.MergeMaps(dynamicValues, generateVariables(parsed, false)),
|
||||||
|
generators.BuildPayloadFromOptions(r.request.options.Options),
|
||||||
|
)
|
||||||
|
|
||||||
|
return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads, interactURL)
|
||||||
|
}
|
||||||
|
values := generators.MergeMaps(
|
||||||
|
dynamicValues,
|
||||||
|
generators.BuildPayloadFromOptions(r.request.options.Options),
|
||||||
|
)
|
||||||
|
return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL)
|
||||||
|
}
|
||||||
|
|
||||||
// Total returns the total number of requests for the generator
|
// Total returns the total number of requests for the generator
|
||||||
func (r *requestGenerator) Total() int {
|
func (r *requestGenerator) Total() int {
|
||||||
if r.payloadIterator != nil {
|
if r.payloadIterator != nil {
|
||||||
|
|
|
@ -46,13 +46,13 @@ type Request struct {
|
||||||
// description: |
|
// description: |
|
||||||
// Attack is the type of payload combinations to perform.
|
// Attack is the type of payload combinations to perform.
|
||||||
//
|
//
|
||||||
// Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
|
// batteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||||
// permutations and combinations for all payloads.
|
// permutations and combinations for all payloads.
|
||||||
// values:
|
// values:
|
||||||
// - "sniper"
|
// - "batteringram"
|
||||||
// - "pitchfork"
|
// - "pitchfork"
|
||||||
// - "clusterbomb"
|
// - "clusterbomb"
|
||||||
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb"`
|
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||||
// description: |
|
// description: |
|
||||||
// Method is the HTTP Request Method.
|
// Method is the HTTP Request Method.
|
||||||
// values:
|
// values:
|
||||||
|
@ -137,6 +137,10 @@ type Request struct {
|
||||||
rawhttpClient *rawhttp.Client
|
rawhttpClient *rawhttp.Client
|
||||||
dynamicValues map[string]interface{}
|
dynamicValues map[string]interface{}
|
||||||
|
|
||||||
|
// description: |
|
||||||
|
// SelfContained specifies if the request is self contained.
|
||||||
|
SelfContained bool `yaml:"-" json:"-"`
|
||||||
|
|
||||||
// description: |
|
// description: |
|
||||||
// CookieReuse is an optional setting that enables cookie reuse for
|
// CookieReuse is an optional setting that enables cookie reuse for
|
||||||
// all requests defined in raw section.
|
// all requests defined in raw section.
|
||||||
|
@ -180,6 +184,10 @@ func (request *Request) GetID() string {
|
||||||
return request.ID
|
return request.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (request *Request) isRaw() bool {
|
||||||
|
return len(request.Raw) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// Compile compiles the protocol request for further execution.
|
// Compile compiles the protocol request for further execution.
|
||||||
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
||||||
connectionConfiguration := &httpclientpool.Configuration{
|
connectionConfiguration := &httpclientpool.Configuration{
|
||||||
|
|
|
@ -147,6 +147,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
|
||||||
IP: types.ToString(wrapped.InternalEvent["ip"]),
|
IP: types.ToString(wrapped.InternalEvent["ip"]),
|
||||||
Request: types.ToString(wrapped.InternalEvent["request"]),
|
Request: types.ToString(wrapped.InternalEvent["request"]),
|
||||||
Response: types.ToString(wrapped.InternalEvent["response"]),
|
Response: types.ToString(wrapped.InternalEvent["response"]),
|
||||||
|
CURLCommand: types.ToString(wrapped.InternalEvent["curl-command"]),
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,8 +90,10 @@ func Parse(request, baseURL string, unsafe bool) (*Request, error) {
|
||||||
return nil, fmt.Errorf("could not parse request URL: %s", parseErr)
|
return nil, fmt.Errorf("could not parse request URL: %s", parseErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
rawRequest.Path = parts[1]
|
rawRequest.Path = parsed.Path
|
||||||
rawRequest.Headers["Host"] = parsed.Host
|
if _, ok := rawRequest.Headers["Host"]; !ok {
|
||||||
|
rawRequest.Headers["Host"] = parsed.Host
|
||||||
|
}
|
||||||
} else if len(parts) > 1 {
|
} else if len(parts) > 1 {
|
||||||
rawRequest.Path = parts[1]
|
rawRequest.Path = parts[1]
|
||||||
}
|
}
|
||||||
|
@ -104,7 +106,9 @@ func Parse(request, baseURL string, unsafe bool) (*Request, error) {
|
||||||
if strings.HasSuffix(parsedURL.Path, "/") && strings.HasPrefix(rawRequest.Path, "/") {
|
if strings.HasSuffix(parsedURL.Path, "/") && strings.HasPrefix(rawRequest.Path, "/") {
|
||||||
parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/")
|
parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/")
|
||||||
}
|
}
|
||||||
rawRequest.Path = fmt.Sprintf("%s%s", parsedURL.Path, rawRequest.Path)
|
if parsedURL.Path != rawRequest.Path {
|
||||||
|
rawRequest.Path = fmt.Sprintf("%s%s", parsedURL.Path, rawRequest.Path)
|
||||||
|
}
|
||||||
if strings.HasSuffix(rawRequest.Path, "//") {
|
if strings.HasSuffix(rawRequest.Path, "//") {
|
||||||
rawRequest.Path = strings.TrimSuffix(rawRequest.Path, "/")
|
rawRequest.Path = strings.TrimSuffix(rawRequest.Path, "/")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
"moul.io/http2curl"
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||||
|
@ -100,6 +101,9 @@ func (request *Request) executeParallelHTTP(reqURL string, dynamicValues output.
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if reqURL == "" {
|
||||||
|
reqURL = generatedHttpRequest.URL()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
||||||
return err
|
return err
|
||||||
|
@ -160,6 +164,9 @@ func (request *Request) executeTurboHTTP(reqURL string, dynamicValues, previous
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if reqURL == "" {
|
||||||
|
reqURL = generatedHttpRequest.URL()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
||||||
return err
|
return err
|
||||||
|
@ -215,6 +222,9 @@ func (request *Request) ExecuteWithResults(reqURL string, dynamicValues, previou
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if reqURL == "" {
|
||||||
|
reqURL = generatedHttpRequest.URL()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
|
||||||
return err
|
return err
|
||||||
|
@ -373,6 +383,16 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var curlCommand string
|
||||||
|
if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil {
|
||||||
|
bodyBytes, _ := generatedRequest.request.BodyBytes()
|
||||||
|
resp.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
command, _ := http2curl.GetCurlCommand(resp.Request)
|
||||||
|
if err == nil && command != nil {
|
||||||
|
curlCommand = command.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gologger.Verbose().Msgf("[%s] Sent HTTP request to %s", request.options.TemplateID, formedURL)
|
gologger.Verbose().Msgf("[%s] Sent HTTP request to %s", request.options.TemplateID, formedURL)
|
||||||
request.options.Output.Request(request.options.TemplateID, formedURL, "http", err)
|
request.options.Output.Request(request.options.TemplateID, formedURL, "http", err)
|
||||||
|
|
||||||
|
@ -429,7 +449,8 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
||||||
redirectedResponse = bytes.ReplaceAll(redirectedResponse, dataOrig, data)
|
redirectedResponse = bytes.ReplaceAll(redirectedResponse, dataOrig, data)
|
||||||
|
|
||||||
// Decode gbk response content-types
|
// Decode gbk response content-types
|
||||||
if contentType := strings.ToLower(resp.Header.Get("Content-Type")); contentType != "" && (strings.Contains(contentType, "gbk") || strings.Contains(contentType, "gb2312")) {
|
// gb18030 supersedes gb2312
|
||||||
|
if isContentTypeGbk(resp.Header.Get("Content-Type")) {
|
||||||
dumpedResponse, err = decodegbk(dumpedResponse)
|
dumpedResponse, err = decodegbk(dumpedResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not gbk decode")
|
return errors.Wrap(err, "could not gbk decode")
|
||||||
|
@ -438,6 +459,12 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not gbk decode")
|
return errors.Wrap(err, "could not gbk decode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the uncompressed body needs to be decoded to standard utf8
|
||||||
|
data, err = decodegbk(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not gbk decode")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if nuclei-project is enabled store the response if not previously done
|
// if nuclei-project is enabled store the response if not previously done
|
||||||
|
@ -460,6 +487,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
||||||
if i := strings.LastIndex(hostname, ":"); i != -1 {
|
if i := strings.LastIndex(hostname, ":"); i != -1 {
|
||||||
hostname = hostname[:i]
|
hostname = hostname[:i]
|
||||||
}
|
}
|
||||||
|
outputEvent["curl-command"] = curlCommand
|
||||||
outputEvent["ip"] = httpclientpool.Dialer.GetDialedIP(hostname)
|
outputEvent["ip"] = httpclientpool.Dialer.GetDialedIP(hostname)
|
||||||
outputEvent["redirect-chain"] = tostring.UnsafeToString(redirectedResponse)
|
outputEvent["redirect-chain"] = tostring.UnsafeToString(redirectedResponse)
|
||||||
for k, v := range previousEvent {
|
for k, v := range previousEvent {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
|
||||||
"github.com/projectdiscovery/rawhttp"
|
"github.com/projectdiscovery/rawhttp"
|
||||||
|
"github.com/projectdiscovery/stringsutil"
|
||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
)
|
)
|
||||||
|
@ -135,3 +136,9 @@ func decodegbk(s []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isContentTypeGbk checks if the content-type header is gbk
|
||||||
|
func isContentTypeGbk(contentType string) bool {
|
||||||
|
contentType = strings.ToLower(contentType)
|
||||||
|
return stringsutil.ContainsAny(contentType, "gbk", "gb2312", "gb18030")
|
||||||
|
}
|
||||||
|
|
|
@ -35,13 +35,13 @@ type Request struct {
|
||||||
// description: |
|
// description: |
|
||||||
// Attack is the type of payload combinations to perform.
|
// Attack is the type of payload combinations to perform.
|
||||||
//
|
//
|
||||||
// Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
|
// Batteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||||
// permutations and combinations for all payloads.
|
// permutations and combinations for all payloads.
|
||||||
// values:
|
// values:
|
||||||
// - "sniper"
|
// - "batteringram"
|
||||||
// - "pitchfork"
|
// - "pitchfork"
|
||||||
// - "clusterbomb"
|
// - "clusterbomb"
|
||||||
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb"`
|
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||||
// description: |
|
// description: |
|
||||||
// Payloads contains any payloads for the current request.
|
// Payloads contains any payloads for the current request.
|
||||||
//
|
//
|
||||||
|
@ -60,6 +60,17 @@ type Request struct {
|
||||||
// examples:
|
// examples:
|
||||||
// - value: "2048"
|
// - value: "2048"
|
||||||
ReadSize int `yaml:"read-size,omitempty" jsonschema:"title=size of network response to read,description=Size of response to read at the end. Default is 1024 bytes"`
|
ReadSize int `yaml:"read-size,omitempty" jsonschema:"title=size of network response to read,description=Size of response to read at the end. Default is 1024 bytes"`
|
||||||
|
// description: |
|
||||||
|
// ReadAll determines if the data stream should be read till the end regardless of the size
|
||||||
|
//
|
||||||
|
// Default value for read-all is false.
|
||||||
|
// examples:
|
||||||
|
// - value: false
|
||||||
|
ReadAll bool `yaml:"read-all,omitempty" jsonschema:"title=read all response stream,description=Read all response stream till the server stops sending"`
|
||||||
|
|
||||||
|
// description: |
|
||||||
|
// SelfContained specifies if the request is self contained.
|
||||||
|
SelfContained bool `yaml:"-" json:"-"`
|
||||||
|
|
||||||
// Operators for the current request go here.
|
// Operators for the current request go here.
|
||||||
operators.Operators `yaml:",inline,omitempty"`
|
operators.Operators `yaml:",inline,omitempty"`
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -26,7 +27,14 @@ var _ protocols.Request = &Request{}
|
||||||
|
|
||||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||||
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||||
address, err := getAddress(input)
|
var address string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if request.SelfContained {
|
||||||
|
address = ""
|
||||||
|
} else {
|
||||||
|
address, err = getAddress(input)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
request.options.Output.Request(request.options.TemplateID, input, "network", err)
|
request.options.Output.Request(request.options.TemplateID, input, "network", err)
|
||||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||||
|
@ -41,6 +49,9 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
||||||
}
|
}
|
||||||
actualAddress = net.JoinHostPort(actualAddress, kv.port)
|
actualAddress = net.JoinHostPort(actualAddress, kv.port)
|
||||||
}
|
}
|
||||||
|
if input != "" {
|
||||||
|
input = actualAddress
|
||||||
|
}
|
||||||
|
|
||||||
if err := request.executeAddress(actualAddress, address, input, kv.tls, previous, callback); err != nil {
|
if err := request.executeAddress(actualAddress, address, input, kv.tls, previous, callback); err != nil {
|
||||||
gologger.Verbose().Label("ERR").Msgf("Could not make network request for %s: %s\n", actualAddress, err)
|
gologger.Verbose().Label("ERR").Msgf("Could not make network request for %s: %s\n", actualAddress, err)
|
||||||
|
@ -59,6 +70,8 @@ func (request *Request) executeAddress(actualAddress, address, input string, sho
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payloads := generators.BuildPayloadFromOptions(request.options.Options)
|
||||||
|
|
||||||
if request.generator != nil {
|
if request.generator != nil {
|
||||||
iterator := request.generator.NewIterator()
|
iterator := request.generator.NewIterator()
|
||||||
|
|
||||||
|
@ -67,12 +80,13 @@ func (request *Request) executeAddress(actualAddress, address, input string, sho
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
value = generators.MergeMaps(value, payloads)
|
||||||
if err := request.executeRequestWithPayloads(actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
|
if err := request.executeRequestWithPayloads(actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value := make(map[string]interface{})
|
value := generators.MergeMaps(map[string]interface{}{}, payloads)
|
||||||
if err := request.executeRequestWithPayloads(actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
|
if err := request.executeRequestWithPayloads(actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -86,6 +100,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
request.dynamicValues = generators.MergeMaps(payloads, map[string]interface{}{"Hostname": address})
|
request.dynamicValues = generators.MergeMaps(payloads, map[string]interface{}{"Hostname": address})
|
||||||
|
|
||||||
if host, _, splitErr := net.SplitHostPort(actualAddress); splitErr == nil {
|
if host, _, splitErr := net.SplitHostPort(actualAddress); splitErr == nil {
|
||||||
|
@ -186,13 +201,48 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
||||||
if request.ReadSize != 0 {
|
if request.ReadSize != 0 {
|
||||||
bufferSize = request.ReadSize
|
bufferSize = request.ReadSize
|
||||||
}
|
}
|
||||||
final := make([]byte, bufferSize)
|
|
||||||
n, err := conn.Read(final)
|
var (
|
||||||
if err != nil && err != io.EOF {
|
final []byte
|
||||||
request.options.Output.Request(request.options.TemplateID, address, "network", err)
|
n int
|
||||||
return errors.Wrap(err, "could not read from server")
|
)
|
||||||
|
|
||||||
|
if request.ReadAll {
|
||||||
|
readInterval := time.NewTimer(time.Second * 1)
|
||||||
|
// stop the timer and drain the channel
|
||||||
|
closeTimer := func(t *time.Timer) {
|
||||||
|
if !t.Stop() {
|
||||||
|
<-t.C
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read_socket:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-readInterval.C:
|
||||||
|
closeTimer(readInterval)
|
||||||
|
break read_socket
|
||||||
|
default:
|
||||||
|
buf := make([]byte, bufferSize)
|
||||||
|
nBuf, err := conn.Read(buf)
|
||||||
|
if err != nil && !os.IsTimeout(err) {
|
||||||
|
request.options.Output.Request(request.options.TemplateID, address, "network", err)
|
||||||
|
closeTimer(readInterval)
|
||||||
|
return errors.Wrap(err, "could not read from server")
|
||||||
|
}
|
||||||
|
responseBuilder.Write(buf[:nBuf])
|
||||||
|
final = append(final, buf...)
|
||||||
|
n += nBuf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final = make([]byte, bufferSize)
|
||||||
|
n, err = conn.Read(final)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
request.options.Output.Request(request.options.TemplateID, address, "network", err)
|
||||||
|
return errors.Wrap(err, "could not read from server")
|
||||||
|
}
|
||||||
|
responseBuilder.Write(final[:n])
|
||||||
}
|
}
|
||||||
responseBuilder.Write(final[:n])
|
|
||||||
|
|
||||||
response := responseBuilder.String()
|
response := responseBuilder.String()
|
||||||
outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress)
|
outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress)
|
||||||
|
|
|
@ -98,6 +98,13 @@ func (request *Request) responseToDSLMap(resp *http.Response, host, matched, raw
|
||||||
for k, v := range extra {
|
for k, v := range extra {
|
||||||
data[k] = v
|
data[k] = v
|
||||||
}
|
}
|
||||||
|
for _, cookie := range resp.Cookies() {
|
||||||
|
data[strings.ToLower(cookie.Name)] = cookie.Value
|
||||||
|
}
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
k = strings.ToLower(strings.TrimSpace(k))
|
||||||
|
data[k] = strings.Join(v, " ")
|
||||||
|
}
|
||||||
|
|
||||||
data["path"] = host
|
data["path"] = host
|
||||||
data["matched"] = matched
|
data["matched"] = matched
|
||||||
|
@ -106,13 +113,6 @@ func (request *Request) responseToDSLMap(resp *http.Response, host, matched, raw
|
||||||
data["content_length"] = resp.ContentLength
|
data["content_length"] = resp.ContentLength
|
||||||
data["status_code"] = resp.StatusCode
|
data["status_code"] = resp.StatusCode
|
||||||
data["body"] = body
|
data["body"] = body
|
||||||
for _, cookie := range resp.Cookies() {
|
|
||||||
data[strings.ToLower(cookie.Name)] = cookie.Value
|
|
||||||
}
|
|
||||||
for k, v := range resp.Header {
|
|
||||||
k = strings.ToLower(strings.TrimSpace(k))
|
|
||||||
data[k] = strings.Join(v, " ")
|
|
||||||
}
|
|
||||||
data["all_headers"] = headers
|
data["all_headers"] = headers
|
||||||
data["duration"] = duration.Seconds()
|
data["duration"] = duration.Seconds()
|
||||||
data["template-id"] = request.options.TemplateID
|
data["template-id"] = request.options.TemplateID
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -22,9 +23,9 @@ type Options struct {
|
||||||
IP string `yaml:"ip"`
|
IP string `yaml:"ip"`
|
||||||
// Port is the port of elasticsearch instance
|
// Port is the port of elasticsearch instance
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
// SSL enables ssl for elasticsearch connection
|
// SSL (optional) enables ssl for elasticsearch connection
|
||||||
SSL bool `yaml:"ssl"`
|
SSL bool `yaml:"ssl"`
|
||||||
// SSLVerification disables SSL verification for elasticsearch
|
// SSLVerification (optional) disables SSL verification for elasticsearch
|
||||||
SSLVerification bool `yaml:"ssl-verification"`
|
SSLVerification bool `yaml:"ssl-verification"`
|
||||||
// Username for the elasticsearch instance
|
// Username for the elasticsearch instance
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
|
@ -49,6 +50,10 @@ type Exporter struct {
|
||||||
// New creates and returns a new exporter for elasticsearch
|
// New creates and returns a new exporter for elasticsearch
|
||||||
func New(option *Options) (*Exporter, error) {
|
func New(option *Options) (*Exporter, error) {
|
||||||
var ei *Exporter
|
var ei *Exporter
|
||||||
|
err := validateOptions(option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 5 * time.Second,
|
Timeout: 5 * time.Second,
|
||||||
|
@ -81,6 +86,31 @@ func New(option *Options) (*Exporter, error) {
|
||||||
return ei, nil
|
return ei, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOptions(options *Options) error {
|
||||||
|
errs := []string{}
|
||||||
|
if options.IP == "" {
|
||||||
|
errs = append(errs, "IP")
|
||||||
|
}
|
||||||
|
if options.Port == 0 {
|
||||||
|
errs = append(errs, "Port")
|
||||||
|
}
|
||||||
|
if options.Username == "" {
|
||||||
|
errs = append(errs, "Username")
|
||||||
|
}
|
||||||
|
if options.Password == "" {
|
||||||
|
errs = append(errs, "Password")
|
||||||
|
}
|
||||||
|
if options.IndexName == "" {
|
||||||
|
errs = append(errs, "IndexName")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New("Mandatory reporting configuration fields are missing: " + strings.Join(errs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Export exports a passed result event to elasticsearch
|
// Export exports a passed result event to elasticsearch
|
||||||
func (i *Exporter) Export(event *output.ResultEvent) error {
|
func (i *Exporter) Export(event *output.ResultEvent) error {
|
||||||
// creating a request
|
// creating a request
|
||||||
|
|
|
@ -131,6 +131,13 @@ func MarkdownDescription(event *output.ResultEvent) string { // TODO remove the
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
builder.WriteString("\n")
|
||||||
|
|
||||||
|
if event.CURLCommand != "" {
|
||||||
|
builder.WriteString("\n**CURL Command**\n```\n")
|
||||||
|
builder.WriteString(event.CURLCommand)
|
||||||
|
builder.WriteString("\n```")
|
||||||
|
}
|
||||||
|
|
||||||
builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei %s](https://github.com/projectdiscovery/nuclei)", config.Version))
|
builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei %s](https://github.com/projectdiscovery/nuclei)", config.Version))
|
||||||
data := builder.String()
|
data := builder.String()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
@ -22,22 +23,29 @@ type Integration struct {
|
||||||
|
|
||||||
// Options contains the configuration options for github issue tracker client
|
// Options contains the configuration options for github issue tracker client
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// BaseURL is the optional self-hosted github application url
|
// BaseURL (optional) is the self-hosted github application url
|
||||||
BaseURL string `yaml:"base-url"`
|
BaseURL string `yaml:"base-url"`
|
||||||
// Username is the username of the github user
|
// Username is the username of the github user
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
// Owner is the owner name of the repository for issues.
|
// Owner (manadatory) is the owner name of the repository for issues.
|
||||||
Owner string `yaml:"owner"`
|
Owner string `yaml:"owner"`
|
||||||
// Token is the token for github account.
|
// Token is the token for github account.
|
||||||
Token string `yaml:"token"`
|
Token string `yaml:"token"`
|
||||||
// ProjectName is the name of the repository.
|
// ProjectName is the name of the repository.
|
||||||
ProjectName string `yaml:"project-name"`
|
ProjectName string `yaml:"project-name"`
|
||||||
// IssueLabel is the label of the created issue type
|
// IssueLabel (optional) is the label of the created issue type
|
||||||
IssueLabel string `yaml:"issue-label"`
|
IssueLabel string `yaml:"issue-label"`
|
||||||
|
// SeverityAsLabel (optional) sends the severity as the label of the created
|
||||||
|
// issue.
|
||||||
|
SeverityAsLabel bool `yaml:"severity-as-label"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new issue tracker integration client based on options.
|
// New creates a new issue tracker integration client based on options.
|
||||||
func New(options *Options) (*Integration, error) {
|
func New(options *Options) (*Integration, error) {
|
||||||
|
err := validateOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ts := oauth2.StaticTokenSource(
|
ts := oauth2.StaticTokenSource(
|
||||||
&oauth2.Token{AccessToken: options.Token},
|
&oauth2.Token{AccessToken: options.Token},
|
||||||
|
@ -50,21 +58,53 @@ func New(options *Options) (*Integration, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not parse custom baseurl")
|
return nil, errors.Wrap(err, "could not parse custom baseurl")
|
||||||
}
|
}
|
||||||
|
if !strings.HasSuffix(parsed.Path, "/") {
|
||||||
|
parsed.Path += "/"
|
||||||
|
}
|
||||||
client.BaseURL = parsed
|
client.BaseURL = parsed
|
||||||
}
|
}
|
||||||
return &Integration{client: client, options: options}, nil
|
return &Integration{client: client, options: options}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOptions(options *Options) error {
|
||||||
|
errs := []string{}
|
||||||
|
if options.Username == "" {
|
||||||
|
errs = append(errs, "Username")
|
||||||
|
}
|
||||||
|
if options.Owner == "" {
|
||||||
|
errs = append(errs, "Owner")
|
||||||
|
}
|
||||||
|
if options.Token == "" {
|
||||||
|
errs = append(errs, "Token")
|
||||||
|
}
|
||||||
|
if options.ProjectName == "" {
|
||||||
|
errs = append(errs, "ProjectName")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New("Mandatory reporting configuration fields are missing: " + strings.Join(errs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateIssue creates an issue in the tracker
|
// CreateIssue creates an issue in the tracker
|
||||||
func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
||||||
summary := format.Summary(event)
|
summary := format.Summary(event)
|
||||||
description := format.MarkdownDescription(event)
|
description := format.MarkdownDescription(event)
|
||||||
|
labels := []string{}
|
||||||
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
||||||
|
if i.options.SeverityAsLabel && severityLabel != "" {
|
||||||
|
labels = append(labels, severityLabel)
|
||||||
|
}
|
||||||
|
if label := i.options.IssueLabel; label != "" {
|
||||||
|
labels = append(labels, label)
|
||||||
|
}
|
||||||
|
|
||||||
req := &github.IssueRequest{
|
req := &github.IssueRequest{
|
||||||
Title: &summary,
|
Title: &summary,
|
||||||
Body: &description,
|
Body: &description,
|
||||||
Labels: &[]string{i.options.IssueLabel, severityLabel},
|
Labels: &labels,
|
||||||
Assignees: &[]string{i.options.Username},
|
Assignees: &[]string{i.options.Username},
|
||||||
}
|
}
|
||||||
_, _, err := i.client.Issues.Create(context.Background(), i.options.Owner, i.options.ProjectName, req)
|
_, _, err := i.client.Issues.Create(context.Background(), i.options.Owner, i.options.ProjectName, req)
|
||||||
|
|
|
@ -2,6 +2,9 @@ package gitlab
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
||||||
|
@ -17,7 +20,7 @@ type Integration struct {
|
||||||
|
|
||||||
// Options contains the configuration options for gitlab issue tracker client
|
// Options contains the configuration options for gitlab issue tracker client
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// BaseURL is the optional self-hosted gitlab application url
|
// BaseURL (optional) is the self-hosted gitlab application url
|
||||||
BaseURL string `yaml:"base-url"`
|
BaseURL string `yaml:"base-url"`
|
||||||
// Username is the username of the gitlab user
|
// Username is the username of the gitlab user
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
|
@ -27,10 +30,17 @@ type Options struct {
|
||||||
ProjectName string `yaml:"project-name"`
|
ProjectName string `yaml:"project-name"`
|
||||||
// IssueLabel is the label of the created issue type
|
// IssueLabel is the label of the created issue type
|
||||||
IssueLabel string `yaml:"issue-label"`
|
IssueLabel string `yaml:"issue-label"`
|
||||||
|
// SeverityAsLabel (optional) sends the severity as the label of the created
|
||||||
|
// issue.
|
||||||
|
SeverityAsLabel bool `yaml:"severity-as-label"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new issue tracker integration client based on options.
|
// New creates a new issue tracker integration client based on options.
|
||||||
func New(options *Options) (*Integration, error) {
|
func New(options *Options) (*Integration, error) {
|
||||||
|
err := validateOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
gitlabOpts := []gitlab.ClientOptionFunc{}
|
gitlabOpts := []gitlab.ClientOptionFunc{}
|
||||||
if options.BaseURL != "" {
|
if options.BaseURL != "" {
|
||||||
gitlabOpts = append(gitlabOpts, gitlab.WithBaseURL(options.BaseURL))
|
gitlabOpts = append(gitlabOpts, gitlab.WithBaseURL(options.BaseURL))
|
||||||
|
@ -46,16 +56,42 @@ func New(options *Options) (*Integration, error) {
|
||||||
return &Integration{client: git, userID: user.ID, options: options}, nil
|
return &Integration{client: git, userID: user.ID, options: options}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOptions(options *Options) error {
|
||||||
|
errs := []string{}
|
||||||
|
if options.Username == "" {
|
||||||
|
errs = append(errs, "Username")
|
||||||
|
}
|
||||||
|
if options.Token == "" {
|
||||||
|
errs = append(errs, "Token")
|
||||||
|
}
|
||||||
|
if options.ProjectName == "" {
|
||||||
|
errs = append(errs, "ProjectName")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New("Mandatory reporting configuration fields are missing: " + strings.Join(errs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateIssue creates an issue in the tracker
|
// CreateIssue creates an issue in the tracker
|
||||||
func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
||||||
summary := format.Summary(event)
|
summary := format.Summary(event)
|
||||||
description := format.MarkdownDescription(event)
|
description := format.MarkdownDescription(event)
|
||||||
|
labels := []string{}
|
||||||
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
||||||
|
if i.options.SeverityAsLabel && severityLabel != "" {
|
||||||
|
labels = append(labels, severityLabel)
|
||||||
|
}
|
||||||
|
if label := i.options.IssueLabel; label != "" {
|
||||||
|
labels = append(labels, label)
|
||||||
|
}
|
||||||
|
|
||||||
_, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
|
_, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
|
||||||
Title: &summary,
|
Title: &summary,
|
||||||
Description: &description,
|
Description: &description,
|
||||||
Labels: gitlab.Labels{i.options.IssueLabel, severityLabel},
|
Labels: labels,
|
||||||
AssigneeIDs: []int{i.userID},
|
AssigneeIDs: []int{i.userID},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -22,9 +23,9 @@ type Integration struct {
|
||||||
|
|
||||||
// Options contains the configuration options for jira client
|
// Options contains the configuration options for jira client
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Cloud value is set to true when Jira cloud is used
|
// Cloud value (optional) is set to true when Jira cloud is used
|
||||||
Cloud bool `yaml:"cloud"`
|
Cloud bool `yaml:"cloud"`
|
||||||
// UpdateExisting value if true, the existing opened issue is updated
|
// UpdateExisting value (optional) if true, the existing opened issue is updated
|
||||||
UpdateExisting bool `yaml:"update-existing"`
|
UpdateExisting bool `yaml:"update-existing"`
|
||||||
// URL is the URL of the jira server
|
// URL is the URL of the jira server
|
||||||
URL string `yaml:"url"`
|
URL string `yaml:"url"`
|
||||||
|
@ -36,12 +37,19 @@ type Options struct {
|
||||||
Token string `yaml:"token"`
|
Token string `yaml:"token"`
|
||||||
// ProjectName is the name of the project.
|
// ProjectName is the name of the project.
|
||||||
ProjectName string `yaml:"project-name"`
|
ProjectName string `yaml:"project-name"`
|
||||||
// IssueType is the name of the created issue type
|
// IssueType (optional) is the name of the created issue type
|
||||||
IssueType string `yaml:"issue-type"`
|
IssueType string `yaml:"issue-type"`
|
||||||
|
// SeverityAsLabel (optional) sends the severity as the label of the created
|
||||||
|
// issue.
|
||||||
|
SeverityAsLabel bool `yaml:"severity-as-label"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new issue tracker integration client based on options.
|
// New creates a new issue tracker integration client based on options.
|
||||||
func New(options *Options) (*Integration, error) {
|
func New(options *Options) (*Integration, error) {
|
||||||
|
err := validateOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
username := options.Email
|
username := options.Email
|
||||||
if !options.Cloud {
|
if !options.Cloud {
|
||||||
username = options.AccountID
|
username = options.AccountID
|
||||||
|
@ -57,10 +65,42 @@ func New(options *Options) (*Integration, error) {
|
||||||
return &Integration{jira: jiraClient, options: options}, nil
|
return &Integration{jira: jiraClient, options: options}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOptions(options *Options) error {
|
||||||
|
errs := []string{}
|
||||||
|
if options.URL == "" {
|
||||||
|
errs = append(errs, "URL")
|
||||||
|
}
|
||||||
|
if options.AccountID == "" {
|
||||||
|
errs = append(errs, "AccountID")
|
||||||
|
}
|
||||||
|
if options.Email == "" {
|
||||||
|
errs = append(errs, "Email")
|
||||||
|
}
|
||||||
|
if options.Token == "" {
|
||||||
|
errs = append(errs, "Token")
|
||||||
|
}
|
||||||
|
if options.ProjectName == "" {
|
||||||
|
errs = append(errs, "ProjectName")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New("Mandatory reporting configuration fields are missing: " + strings.Join(errs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNewIssue creates a new issue in the tracker
|
// CreateNewIssue creates a new issue in the tracker
|
||||||
func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
||||||
summary := format.Summary(event)
|
summary := format.Summary(event)
|
||||||
severityLabel := fmt.Sprintf("Severity:%s", event.Info.SeverityHolder.Severity.String())
|
labels := []string{}
|
||||||
|
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
||||||
|
if i.options.SeverityAsLabel && severityLabel != "" {
|
||||||
|
labels = append(labels, severityLabel)
|
||||||
|
}
|
||||||
|
if label := i.options.IssueType; label != "" {
|
||||||
|
labels = append(labels, label)
|
||||||
|
}
|
||||||
|
|
||||||
fields := &jira.IssueFields{
|
fields := &jira.IssueFields{
|
||||||
Assignee: &jira.User{AccountID: i.options.AccountID},
|
Assignee: &jira.User{AccountID: i.options.AccountID},
|
||||||
|
@ -69,7 +109,7 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
||||||
Type: jira.IssueType{Name: i.options.IssueType},
|
Type: jira.IssueType{Name: i.options.IssueType},
|
||||||
Project: jira.Project{Key: i.options.ProjectName},
|
Project: jira.Project{Key: i.options.ProjectName},
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
Labels: []string{severityLabel},
|
Labels: labels,
|
||||||
}
|
}
|
||||||
// On-prem version of Jira server does not use AccountID
|
// On-prem version of Jira server does not use AccountID
|
||||||
if !i.options.Cloud {
|
if !i.options.Cloud {
|
||||||
|
@ -244,6 +284,13 @@ func jiraFormatDescription(event *output.ResultEvent) string { // TODO remove th
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
builder.WriteString("\n")
|
||||||
|
|
||||||
|
if event.CURLCommand != "" {
|
||||||
|
builder.WriteString("\n*CURL Command*\n{code}\n")
|
||||||
|
builder.WriteString(event.CURLCommand)
|
||||||
|
builder.WriteString("\n{code}")
|
||||||
|
}
|
||||||
builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei v%s](https://github.com/projectdiscovery/nuclei)", config.Version))
|
builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei v%s](https://github.com/projectdiscovery/nuclei)", config.Version))
|
||||||
data := builder.String()
|
data := builder.String()
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -144,6 +144,21 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Execute
|
||||||
}
|
}
|
||||||
template.Path = filePath
|
template.Path = filePath
|
||||||
|
|
||||||
|
template.parseSelfContainedRequests()
|
||||||
|
|
||||||
parsedTemplatesCache.Store(filePath, template, err)
|
parsedTemplatesCache.Store(filePath, template, err)
|
||||||
return template, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSelfContainedRequests parses the self contained template requests.
|
||||||
|
func (t *Template) parseSelfContainedRequests() {
|
||||||
|
if !t.SelfContained {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, request := range t.RequestsHTTP {
|
||||||
|
request.SelfContained = true
|
||||||
|
}
|
||||||
|
for _, request := range t.RequestsNetwork {
|
||||||
|
request.SelfContained = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Template struct {
|
||||||
// examples:
|
// examples:
|
||||||
// - name: ID Example
|
// - name: ID Example
|
||||||
// value: "\"CVE-2021-19520\""
|
// value: "\"CVE-2021-19520\""
|
||||||
ID string `yaml:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520"`
|
ID string `yaml:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
|
||||||
// description: |
|
// description: |
|
||||||
// Info contains metadata information about the template.
|
// Info contains metadata information about the template.
|
||||||
// examples:
|
// examples:
|
||||||
|
@ -62,6 +62,10 @@ type Template struct {
|
||||||
workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
|
workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
|
||||||
CompiledWorkflow *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`
|
CompiledWorkflow *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`
|
||||||
|
|
||||||
|
// description: |
|
||||||
|
// Self Contained marks Requests for the template as self-contained
|
||||||
|
SelfContained bool `yaml:"self-contained,omitempty" jsonschema:"title=mark requests as self-contained,description=Mark Requests for the template as self-contained"`
|
||||||
|
|
||||||
// TotalRequests is the total number of requests for the template.
|
// TotalRequests is the total number of requests for the template.
|
||||||
TotalRequests int `yaml:"-" json:"-"`
|
TotalRequests int `yaml:"-" json:"-"`
|
||||||
// Executer is the actual template executor for running template requests
|
// Executer is the actual template executor for running template requests
|
||||||
|
|
|
@ -31,7 +31,7 @@ func init() {
|
||||||
TemplateDoc.Type = "Template"
|
TemplateDoc.Type = "Template"
|
||||||
TemplateDoc.Comments[encoder.LineComment] = " Template is a YAML input file which defines all the requests and"
|
TemplateDoc.Comments[encoder.LineComment] = " Template is a YAML input file which defines all the requests and"
|
||||||
TemplateDoc.Description = "Template is a YAML input file which defines all the requests and\n other metadata for a template."
|
TemplateDoc.Description = "Template is a YAML input file which defines all the requests and\n other metadata for a template."
|
||||||
TemplateDoc.Fields = make([]encoder.Doc, 8)
|
TemplateDoc.Fields = make([]encoder.Doc, 9)
|
||||||
TemplateDoc.Fields[0].Name = "id"
|
TemplateDoc.Fields[0].Name = "id"
|
||||||
TemplateDoc.Fields[0].Type = "string"
|
TemplateDoc.Fields[0].Type = "string"
|
||||||
TemplateDoc.Fields[0].Note = ""
|
TemplateDoc.Fields[0].Note = ""
|
||||||
|
@ -84,6 +84,11 @@ func init() {
|
||||||
TemplateDoc.Fields[7].Note = ""
|
TemplateDoc.Fields[7].Note = ""
|
||||||
TemplateDoc.Fields[7].Description = "Workflows is a list of workflows to execute for a template."
|
TemplateDoc.Fields[7].Description = "Workflows is a list of workflows to execute for a template."
|
||||||
TemplateDoc.Fields[7].Comments[encoder.LineComment] = "Workflows is a list of workflows to execute for a template."
|
TemplateDoc.Fields[7].Comments[encoder.LineComment] = "Workflows is a list of workflows to execute for a template."
|
||||||
|
TemplateDoc.Fields[8].Name = "self-contained"
|
||||||
|
TemplateDoc.Fields[8].Type = "bool"
|
||||||
|
TemplateDoc.Fields[8].Note = ""
|
||||||
|
TemplateDoc.Fields[8].Description = "Self Contained marks Requests for the template as self-contained"
|
||||||
|
TemplateDoc.Fields[8].Comments[encoder.LineComment] = "Self Contained marks Requests for the template as self-contained"
|
||||||
|
|
||||||
MODELInfoDoc.Type = "model.Info"
|
MODELInfoDoc.Type = "model.Info"
|
||||||
MODELInfoDoc.Comments[encoder.LineComment] = " Info contains metadata information about a template"
|
MODELInfoDoc.Comments[encoder.LineComment] = " Info contains metadata information about a template"
|
||||||
|
@ -317,10 +322,10 @@ func init() {
|
||||||
HTTPRequestDoc.Fields[7].Name = "attack"
|
HTTPRequestDoc.Fields[7].Name = "attack"
|
||||||
HTTPRequestDoc.Fields[7].Type = "string"
|
HTTPRequestDoc.Fields[7].Type = "string"
|
||||||
HTTPRequestDoc.Fields[7].Note = ""
|
HTTPRequestDoc.Fields[7].Note = ""
|
||||||
HTTPRequestDoc.Fields[7].Description = "Attack is the type of payload combinations to perform.\n\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads."
|
HTTPRequestDoc.Fields[7].Description = "Attack is the type of payload combinations to perform.\n\nbatteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads."
|
||||||
HTTPRequestDoc.Fields[7].Comments[encoder.LineComment] = "Attack is the type of payload combinations to perform."
|
HTTPRequestDoc.Fields[7].Comments[encoder.LineComment] = "Attack is the type of payload combinations to perform."
|
||||||
HTTPRequestDoc.Fields[7].Values = []string{
|
HTTPRequestDoc.Fields[7].Values = []string{
|
||||||
"sniper",
|
"batteringram",
|
||||||
"pitchfork",
|
"pitchfork",
|
||||||
"clusterbomb",
|
"clusterbomb",
|
||||||
}
|
}
|
||||||
|
@ -838,7 +843,7 @@ func init() {
|
||||||
FieldName: "network",
|
FieldName: "network",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
NETWORKRequestDoc.Fields = make([]encoder.Doc, 9)
|
NETWORKRequestDoc.Fields = make([]encoder.Doc, 10)
|
||||||
NETWORKRequestDoc.Fields[0].Name = "id"
|
NETWORKRequestDoc.Fields[0].Name = "id"
|
||||||
NETWORKRequestDoc.Fields[0].Type = "string"
|
NETWORKRequestDoc.Fields[0].Type = "string"
|
||||||
NETWORKRequestDoc.Fields[0].Note = ""
|
NETWORKRequestDoc.Fields[0].Note = ""
|
||||||
|
@ -854,10 +859,10 @@ func init() {
|
||||||
NETWORKRequestDoc.Fields[2].Name = "attack"
|
NETWORKRequestDoc.Fields[2].Name = "attack"
|
||||||
NETWORKRequestDoc.Fields[2].Type = "string"
|
NETWORKRequestDoc.Fields[2].Type = "string"
|
||||||
NETWORKRequestDoc.Fields[2].Note = ""
|
NETWORKRequestDoc.Fields[2].Note = ""
|
||||||
NETWORKRequestDoc.Fields[2].Description = "Attack is the type of payload combinations to perform.\n\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads."
|
NETWORKRequestDoc.Fields[2].Description = "Attack is the type of payload combinations to perform.\n\nBatteringram is same payload into all of the defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads."
|
||||||
NETWORKRequestDoc.Fields[2].Comments[encoder.LineComment] = "Attack is the type of payload combinations to perform."
|
NETWORKRequestDoc.Fields[2].Comments[encoder.LineComment] = "Attack is the type of payload combinations to perform."
|
||||||
NETWORKRequestDoc.Fields[2].Values = []string{
|
NETWORKRequestDoc.Fields[2].Values = []string{
|
||||||
"sniper",
|
"batteringram",
|
||||||
"pitchfork",
|
"pitchfork",
|
||||||
"clusterbomb",
|
"clusterbomb",
|
||||||
}
|
}
|
||||||
|
@ -878,22 +883,29 @@ func init() {
|
||||||
NETWORKRequestDoc.Fields[5].Comments[encoder.LineComment] = "ReadSize is the size of response to read at the end"
|
NETWORKRequestDoc.Fields[5].Comments[encoder.LineComment] = "ReadSize is the size of response to read at the end"
|
||||||
|
|
||||||
NETWORKRequestDoc.Fields[5].AddExample("", 2048)
|
NETWORKRequestDoc.Fields[5].AddExample("", 2048)
|
||||||
NETWORKRequestDoc.Fields[6].Name = "matchers"
|
NETWORKRequestDoc.Fields[6].Name = "read-all"
|
||||||
NETWORKRequestDoc.Fields[6].Type = "[]matchers.Matcher"
|
NETWORKRequestDoc.Fields[6].Type = "bool"
|
||||||
NETWORKRequestDoc.Fields[6].Note = ""
|
NETWORKRequestDoc.Fields[6].Note = ""
|
||||||
NETWORKRequestDoc.Fields[6].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
|
NETWORKRequestDoc.Fields[6].Description = "ReadAll determines if the data stream should be read till the end regardless of the size\n\nDefault value for read-all is false."
|
||||||
NETWORKRequestDoc.Fields[6].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
|
NETWORKRequestDoc.Fields[6].Comments[encoder.LineComment] = "ReadAll determines if the data stream should be read till the end regardless of the size"
|
||||||
NETWORKRequestDoc.Fields[7].Name = "extractors"
|
|
||||||
NETWORKRequestDoc.Fields[7].Type = "[]extractors.Extractor"
|
NETWORKRequestDoc.Fields[6].AddExample("", false)
|
||||||
|
NETWORKRequestDoc.Fields[7].Name = "matchers"
|
||||||
|
NETWORKRequestDoc.Fields[7].Type = "[]matchers.Matcher"
|
||||||
NETWORKRequestDoc.Fields[7].Note = ""
|
NETWORKRequestDoc.Fields[7].Note = ""
|
||||||
NETWORKRequestDoc.Fields[7].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
|
NETWORKRequestDoc.Fields[7].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
|
||||||
NETWORKRequestDoc.Fields[7].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
|
NETWORKRequestDoc.Fields[7].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
|
||||||
NETWORKRequestDoc.Fields[8].Name = "matchers-condition"
|
NETWORKRequestDoc.Fields[8].Name = "extractors"
|
||||||
NETWORKRequestDoc.Fields[8].Type = "string"
|
NETWORKRequestDoc.Fields[8].Type = "[]extractors.Extractor"
|
||||||
NETWORKRequestDoc.Fields[8].Note = ""
|
NETWORKRequestDoc.Fields[8].Note = ""
|
||||||
NETWORKRequestDoc.Fields[8].Description = "MatchersCondition is the condition between the matchers. Default is OR."
|
NETWORKRequestDoc.Fields[8].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
|
||||||
NETWORKRequestDoc.Fields[8].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
|
NETWORKRequestDoc.Fields[8].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
|
||||||
NETWORKRequestDoc.Fields[8].Values = []string{
|
NETWORKRequestDoc.Fields[9].Name = "matchers-condition"
|
||||||
|
NETWORKRequestDoc.Fields[9].Type = "string"
|
||||||
|
NETWORKRequestDoc.Fields[9].Note = ""
|
||||||
|
NETWORKRequestDoc.Fields[9].Description = "MatchersCondition is the condition between the matchers. Default is OR."
|
||||||
|
NETWORKRequestDoc.Fields[9].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
|
||||||
|
NETWORKRequestDoc.Fields[9].Values = []string{
|
||||||
"and",
|
"and",
|
||||||
"or",
|
"or",
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,8 @@ type Options struct {
|
||||||
Stdin bool
|
Stdin bool
|
||||||
// StopAtFirstMatch stops processing template at first full match (this may break chained requests)
|
// StopAtFirstMatch stops processing template at first full match (this may break chained requests)
|
||||||
StopAtFirstMatch bool
|
StopAtFirstMatch bool
|
||||||
|
// Stream the input without sorting
|
||||||
|
Stream bool
|
||||||
// NoMeta disables display of metadata for the matches
|
// NoMeta disables display of metadata for the matches
|
||||||
NoMeta bool
|
NoMeta bool
|
||||||
// NoTimestamp disables display of timestamp for the matcher
|
// NoTimestamp disables display of timestamp for the matcher
|
||||||
|
|
Loading…
Reference in New Issue