diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8173b810..252203d0 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,7 +12,7 @@ jobs: name: Test Builds strategy: matrix: - go-version: [1.20.x] + go-version: [1.21.x] os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} @@ -22,6 +22,11 @@ jobs: with: go-version: ${{ matrix.go-version }} + - name: Set up Python # required for running python code in py-snippet.yaml integration test + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Check out code uses: actions/checkout@v3 @@ -56,6 +61,10 @@ jobs: run: go run -race . -l ../functional-test/targets.txt -id tech-detect,tls-version working-directory: v2/cmd/nuclei/ - - name: Example Code Tests - run: go build . - working-directory: v2/examples/ + - name: Example SDK Simple + run: go run . + working-directory: v2/examples/simple/ + + - name: Example SDK Advanced + run: go run . + working-directory: v2/examples/advanced/ diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 79b154af..a6bafd84 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Check out code uses: actions/checkout@v3 diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 9c93c088..24ba452e 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/performance-test.yaml b/.github/workflows/performance-test.yaml index 09c3f276..2e597723 100644 --- a/.github/workflows/performance-test.yaml +++ b/.github/workflows/performance-test.yaml @@ -11,7 +11,7 @@ jobs: name: Test Performance strategy: matrix: - go-version: [1.20.x] + go-version: [1.21.x] os: [ubuntu-latest, macOS-latest] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index c7d0f386..2fca548a 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -18,7 +18,7 @@ jobs: - name: "Set up Go" uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Generate YAML Syntax Documentation id: generate-docs diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index 4a40bf9e..314d7b06 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - uses: goreleaser/goreleaser-action@v4 with: diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index 26917f0e..df645d6e 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: release test uses: goreleaser/goreleaser-action@v4 diff --git a/.github/workflows/template-validate.yml b/.github/workflows/template-validate.yml index 5ebb7428..566e5ed3 100644 --- a/.github/workflows/template-validate.yml +++ b/.github/workflows/template-validate.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Template Validation run: | diff --git a/.gitignore b/.gitignore index 471e0385..ea395911 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,11 @@ v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject.class v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser *.exe v2/.gitignore +v2/pkg/js/devtools/bindgen/cmd/bindgen +v2/pkg/js/devtools/jsdocgen/jsdocgen +bindgen +jsdocgen +nuclei +v2/scrapefuncs *.DS_Store +v2/pkg/protocols/headless/engine/.cache diff --git a/DEBUG.md b/DEBUG.md index d217a125..8d94b51f 100644 --- a/DEBUG.md +++ b/DEBUG.md @@ -34,9 +34,12 @@ When this flag is provided, nuclei will log all errors to the file specified. Th Nuclei was built with some environment variables in mind to help with debugging. These environment variables can be set to enable debugging of a particular component/functionality for nuclei. -| Environment Variable | Description | -| ---------------------- | -------------------------------------------------------- | -| `DEBUG=true` | Enables Printing Stack Traces for all errors | -| `SHOW_DSL_ERRORS=true` | Enables Printing DSL Errors (that are hidden by default) | +| Environment Variable | Description | +| -------------------------------- | -------------------------------------------------------- | +| `DEBUG=true` | Enables Printing Stack Traces for all errors | +| `SHOW_DSL_ERRORS=true` | Enables Printing DSL Errors (that are hidden by default) | +| `HIDE_TEMPLATE_SIG_WARNING=true` | Hides Template Signature Verification Warnings | +| `NUCLEI_LOG_ALL=true` | Log All Events that were skipped in verbose mode | + diff --git a/Dockerfile b/Dockerfile index 23a40a61..1c609082 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build -FROM golang:1.20.6-alpine AS build-env +FROM golang:1.21-alpine AS build-env RUN apk add build-base WORKDIR /app COPY . /app diff --git a/README.md b/README.md index 8c770a23..49f6dc0e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ --- -Nuclei is used to send requests across targets based on a template, leading to zero false positives and providing fast scanning on a large number of hosts. Nuclei offers scanning for a variety of protocols, including TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless etc. With powerful and flexible templating, Nuclei can be used to model all kinds of security checks. +Nuclei is used to send requests across targets based on a template, leading to zero false positives and providing fast scanning on a large number of hosts. Nuclei offers scanning for a variety of protocols, including TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, Code etc. With powerful and flexible templating, Nuclei can be used to model all kinds of security checks. We have a [dedicated repository](https://github.com/projectdiscovery/nuclei-templates) that houses various type of vulnerability templates contributed by **more than 300** security researchers and engineers. @@ -56,7 +56,7 @@ We have a [dedicated repository](https://github.com/projectdiscovery/nuclei-temp # Install Nuclei -Nuclei requires **go1.20** to install successfully. Run the following command to install the latest version - +Nuclei requires **go1.21** to install successfully. Run the following command to install the latest version - ```sh go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest @@ -146,8 +146,8 @@ FILTERING: -em, -exclude-matchers string[] template matchers to exclude in result -s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown -es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown - -pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois - -ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois + -pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code + -ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code -tc, -template-condition string[] templates to run based on expression condition OUTPUT: @@ -379,7 +379,7 @@ We have [a discussion thread around this](https://github.com/projectdiscovery/nu ### Using Nuclei From Go Code -Examples of using Nuclei From Go Code to run templates on targets are provided in the [examples](v2/examples/) folder. +Complete guide of using Nuclei as Library/SDK is available at [lib](v2/lib/README.md) ### Resources diff --git a/README_CN.md b/README_CN.md index d39738c0..79c4aaa2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -52,7 +52,7 @@ Nuclei使用零误报的定制模板向目标发送请求,同时可以对主 # 安装Nuclei -Nuclei需要**go1.20**才能安装成功。执行下列命令安装最新版本的Nuclei +Nuclei需要**go1**才能安装成功。执行下列命令安装最新版本的Nuclei ```sh go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest diff --git a/README_ID.md b/README_ID.md index 04fcd3e8..55bc9584 100644 --- a/README_ID.md +++ b/README_ID.md @@ -52,7 +52,7 @@ Kami memiliki [repositori khusus](https://github.com/projectdiscovery/nuclei-tem # Instalasi Nuclei -Nuclei membutuhkan **go1.20** agar dapat diinstall. Jalankan perintah berikut untuk menginstal versi terbaru - +Nuclei membutuhkan **go1.21** agar dapat diinstall. Jalankan perintah berikut untuk menginstal versi terbaru - ```sh go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest diff --git a/README_KR.md b/README_KR.md index 9608083d..49e874ee 100644 --- a/README_KR.md +++ b/README_KR.md @@ -50,7 +50,7 @@ Nuclei는 템플릿을 기반으로 대상 간에 요청을 보내기 위해 사 # 설치 -Nuclei를 성공적으로 설치하기 위해서 **go1.20**가 필요합니다. 다음 명령을 실행하여 최신 버전을 설치합니다. +Nuclei를 성공적으로 설치하기 위해서 **go1.21**가 필요합니다. 다음 명령을 실행하여 최신 버전을 설치합니다. ```sh go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md index c03cd6b3..48848787 100755 --- a/SYNTAX-REFERENCE.md +++ b/SYNTAX-REFERENCE.md @@ -2820,28 +2820,6 @@ StopAtFirstMatch stops the execution of the requests and template as soon as a m
-Fuzzing describes schema to fuzz headless requests - -
- -
- -
- -cookie-reuse bool - -
-
- -CookieReuse is an optional setting that enables cookie reuse - -
- -
- - - - ## engine.Action Action is an action taken by the browser to reach a navigation diff --git a/docs/getting-started/running.mdx b/docs/getting-started/running.mdx index 44cdadce..6b546a94 100644 --- a/docs/getting-started/running.mdx +++ b/docs/getting-started/running.mdx @@ -178,6 +178,25 @@ export AZURE_CONTAINER_NAME=templates +Environment variables can also be provided to disable download from default and custom template locations: + +```bash +# Disable download from the default nuclei-templates project +export DISABLE_NUCLEI_TEMPLATES_PUBLIC_DOWNLOAD=true + +# Disable download from public / private GitHub project(s) +export DISABLE_NUCLEI_TEMPLATES_GITHUB_DOWNLOAD=true + +# Disable download from public / private GitLab project(s) +export DISABLE_NUCLEI_TEMPLATES_GITLAB_DOWNLOAD=true + +# Disable download from public / private AWS Bucket(s) +export DISABLE_NUCLEI_TEMPLATES_AWS_DOWNLOAD=true + +# Disable download from public / private Azure Blob Storage +export DISABLE_NUCLEI_TEMPLATES_AZURE_DOWNLOAD=true +``` + Once the environment variables are set, following command to download the custom templates: ```bash diff --git a/docs/mint.json b/docs/mint.json index 75713010..42ec2179 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -57,7 +57,7 @@ "template-guide/introduction", "template-guide/template-details", { - "group":"HTTP", + "group":"HTTPs", "pages":[ "template-guide/http/base-http", "template-guide/http/raw-http", @@ -71,6 +71,8 @@ "template-guide/network", "template-guide/dns", "template-guide/file", + "template-guide/javascript", + "template-guide/code", { "group":"Operators", "pages":[ @@ -140,11 +142,5 @@ "feedback":{ "thumbsRating":true, "suggestEdit":true - }, - "api":{ - "auth":{ - "method":"key", - "name":"X-API-KEY" - } } } \ No newline at end of file diff --git a/docs/template-guide/code.mdx b/docs/template-guide/code.mdx new file mode 100644 index 00000000..01ed003b --- /dev/null +++ b/docs/template-guide/code.mdx @@ -0,0 +1,342 @@ +--- +title: "Code" +--- + +## Code Requests (beta) + +Nuclei enables the execution of external code on the host operating system. This feature allows security researchers, pentesters, and developers to extend the capabilities of Nuclei and perform complex actions beyond the scope of regular supported protocol-based testing. + +By leveraging this capability, Nuclei can interact with the underlying operating system and execute custom scripts or commands, opening up a wide range of possibilities. It enables users to perform tasks such as system-level configurations, file operations, network interactions, and more. This level of control and flexibility empowers users to tailor their security testing workflows according to their specific requirements. + +However, it's important to exercise caution while utilizing this feature, as executing external code on the host operating system carries inherent risks. It is crucial to ensure that the executed code is secure, thoroughly tested, and does not pose any unintended consequences or security risks to the target system. + +## Template Signing (beta) + +Template signing via the private-public key mechanism is a crucial aspect of ensuring the integrity and authenticity of templates. This mechanism involves the use of asymmetric cryptography, specifically ECDSA algorithm, to create a secure and verifiable signature. + +In this process, a template author generates a private key that remains confidential and securely stored. The corresponding public key is then shared with the template consumers. When a template is created or modified, the author signs it using their private key, generating a unique signature that is attached to the template. + +Template consumers can verify the authenticity and integrity of a signed template by using the author's public key. By applying the appropriate cryptographic algorithm (ECDSA), they can validate the signature and ensure that the template has not been tampered with since it was signed. This provides a level of trust, as any modifications or unauthorized changes to the template would result in a failed verification process. + +By employing the private-public key mechanism, template signing adds an additional layer of security and trust to the template ecosystem. It helps establish the identity of the template author and ensures that the templates used in various systems are genuine and have not been altered maliciously. + +### What does signing a template mean ? + +Template signing is a mechanism to ensure the integrity and authenticity of templates. The primary goal is to provide template writers/consumers a way to trust crowdsource/custom templates ensuring that they are not tampered + +All Official nuclei templates include a digital signature in them and are verified by nuclei while loading templates using ProjectDiscovery's public key shipped with nuclei binary itself. + +Individuals / Organizations running nuclei in their work environment can generate their own key-pair with `nuclei` and sign their custom templates with their private key, thus ensuring that only authorized templates are being used in their environment. + +This also allows entities to fully utilize the power of new protocols like `code` and `javascript` without worrying about malicious custom templates being used in their environment. + +**Points to note** + +- Template signing is optional for all protocols except `code`. + +- Code File References (ex: `source: protocols/code/pyfile.py`) are allowed and content of these files is included in the template digest + +- Payload File References (ex: `payloads: protocols/http/params.txt`) are not included in the template digest as it is treated as a payload/helper and not actual code that is being executed + +- Template Signing is deterministic while both signing and verifying a template i.e if a code file is referenced in a template that is present outside of templates directory with `-lfa` flag then verification will fail if same template is used without `-lfa` flag. (Note this only applies to `-lfa` i.e local file access flag only) + +## How to sign custom templates + +Simplest and recommended way to generate key-pair and signing/verfifying templates is to use `nuclei` itself. + +#### When Signing a template if key-pair does not exist then nuclei will prompt user to generate a new key-pair with options + +```console +$ ./nuclei -t my-template.yaml -sign -v +[INF] Generating new key-pair for signing templates +[*] Enter User/Organization Name (exit to abort) : projectdiscovery/nuclei-templates +[*] Enter passphrase (exit to abort): +[*] Enter same passphrase again: +[INF] Successfully generated new key-pair for signing templates + +``` + +> Note: Passphrase is optional and can be left blank when used private key is encrypted with passphrase using PEMCipherAES256 Algo + +#### Signing a template with existing key-pair + +```console +$ ./nuclei -t ~/nuclei-templates/http -sign -v +[INF] All templates signatures were elaborated success=6464 failed=0 +``` + +### Template Digest + +When a template is signed, a digest is generated and added to the template. This digest is a hash of the template content and is used to verify the integrity of the template. If the template is modified after signing, the digest will change, and the signature verification will fail which happens during template loading. + +```yaml +# digest: 4a0a00473045022100eb01da6b97893e7868c584f330a0cd52df9bddac005860bb8595ba5b8aed58c9022050043feac68d69045cf320cba9298a2eb2e792ea4720d045d01e803de1943e7d:4a3eb6b4988d95847d4203be25ed1d46 +``` + +It is in the format of `signature:fragment` where signature is digital signature of template which is used to verify integrity of template +and fragment is a metadata generated by md5 hashing public key to disable re-signing of code templates not written by you. + +fragment is meant to act like a speed bump to prevent mass-signing of code protocol templates to prevent any unintended misuse. + +### Where are keys stored ? + +key-pair generated by nuclei are stored in 2 files in `$config/nuclei/keys` directory where `$config` is system specific config directory + +``` +$ la ~/.config/nuclei/keys +total 16 +-rw------- 1 tarun staff 251B Oct 4 21:45 nuclei-user-private-key.pem # encrypted private key with passphrase +-rw------- 1 tarun staff 572B Oct 4 21:45 nuclei-user.crt # self signed certificate which includes public key and identifier (i.e user/org name) +``` + +### Sharing and Using Public Key + +Public key is stored in $config/nuclei/keys/nuclei-user.crt and can be shared with other users / organizations to verify templates signed by you. + +#### Using Public Key + +- A simple way to use public key is to copy it to $config/nuclei/keys directory of other user's machine + +- Another way is to use environment variable `NUCLEI_USER_CERTIFICATE=xxx` to specify path of public key or content of public key directly + +```console +$ export NUCLEI_USER_CERTIFICATE=path/to/nuclei-user.crt +``` + +or + +```console +$ export NUCLEI_USER_CERTIFICATE=$(cat path/to/nuclei-user.crt) +``` + +#### Verifying Templates + +Everytime `nuclei` is run, it loads user certificate (aka public key) from above locations and uses it to verify templates. + +`nuclei` also prints identifier of public key being used and warns user of unsigned custom templates + +``` +[INF] Executing 6219 signed templates from projectdiscovery/nuclei-templates +[WRN] Executing 687 unsigned templates. Use with caution. +``` + +### Managing Private Key + +Private key is stored in $config/nuclei/keys/nuclei-user-private-key.pem and is encrypted with passphrase if provided while generating key-pair. + +It is not used/loaded by default by nuclei and is only used on demand i.e when signing templates using `-sign` flag + +Some Users might want to store / backup or move private key to different location or machine and `nuclei` doesn't enforce any restrictions on that. + +#### Using Private Key + +- A simple way to use private key is to copy it to $config/nuclei/keys directory of other user's machine + +- Another way is to use environment variable `NUCLEI_USER_PRIVATE_KEY=xxx` to specify path of private key or content of private key directly + +```console +$ export NUCLEI_USER_PRIVATE_KEY=path/to/nuclei-user-private-key.pem +``` + +or + +```console +$ export NUCLEI_USER_PRIVATE_KEY=$(cat path/to/nuclei-user-private-key.pem) +``` + +> Note: You are responsible for securing and managing private key and nuclei has no accountability for any loss of private key + + +## Code + +In the context of template creation, a code block is used to indicate the start of the requests for the template. This block marks the beginning of the code-related instructions. + +```yaml +# Start the requests for the template right here +code: +``` + +To execute the code, a list of engines is specified, which are searched sequentially until a valid one is found on the system. The engine names must match the corresponding binary names on the system. + +```yaml +- engine: + - py + - python3 +``` + +The code to be executed can be provided either as an external file or as a code snippet directly within the template. + +For an external file: + +```yaml +source: protocols/code/pyfile.py +``` + +For a code snippet: +```yaml +source: | + import sys + print("hello from " + sys.stdin.read()) +``` + +The target is passed to the template via stdin, and the output of the executed code is available for further processing in matchers and extractors. In the case of the Code protocol, the body part represents all data printed to stdout during the execution of the code. + +#### Matchers / Extractor Parts + +Valid `part` values supported by **Code** protocol for Matchers / Extractor are - + +| Value | Description | +| -------- | ---------------------------------------------------- | +| response | execution output (trailing whitespaces are filtered) | +| stderr | Raw Stderr Output(if any) | + + +#### **Example Code Template** + +The provided example demonstrates the execution of a Python script within the template. The specified engines are searched in the given order, and the code snippet is executed accordingly. Additionally, a matcher is included to check if the code's stdout contains the phrase "hello from input." (input must be passed as target with nuclei) + +```yaml +id: py-code-snippet + +info: + name: py-code-snippet + author: pdteam + severity: info + tags: code + description: | + py-code-snippet + +code: + - engine: + - py + - python3 + source: | + import sys + print("hello from " + sys.stdin.read()) + + matchers: + - type: word + words: + - "hello from input" +# digest: 4a0a00473045022067a69eb337ffa56d1c8e2cc57b7f74a5eb3294e6f366c9074778b2da3f1d795d02210096d6acda6acd2fe0ff005b08a9c0b72b63f599532ec6493f44b8518265d0e5fd:4a3eb6b4988d95847d4203be25ed1d46 +``` + +### Optional Fields for Code Protocol + +Apart from required fields mentioned above, Code protocol also supports following optional fields to further customize the execution of code. + +#### Args + +Args are arguments that are sent to engine while executing the code. For example if we want to bypass execution policy in powershell for specific template this can be done by adding following args to the template. + +```yaml + - engine: + - powershell + - powershell.exe + args: + - -ExecutionPolicy + - Bypass + - -File +``` + +#### Pattern + +Pattern field can be used to customize name / extension of temporary file while executing a code snippet in a template + +```yaml + pattern: "*.ps1" +``` + +adding `pattern: "*.ps1"` will make sure that name of temporary file given pattern + + +### Example Code Template with Args and Pattern + +Below is a example code template where we are executing a powershell script while customizing behaviour of execution policy and setting pattern to `*.ps1` + +```yaml +id: ps1-code-snippet + +info: + name: ps1-code-snippet + author: pdteam + severity: info + tags: code + description: | + ps1-code-snippet + +code: + - engine: + - powershell + - powershell.exe + args: + - -ExecutionPolicy + - Bypass + - -File + pattern: "*.ps1" + source: | + $stdin = [Console]::In + $line = $stdin.ReadLine() + Write-Host "hello from $line" + + matchers: + - type: word + words: + - "hello from input" +# digest: 4a0a00473045022100eb01da6b97893e7868c584f330a0cd52df9bddac005860bb8595ba5b8aed58c9022050043feac68d69045cf320cba9298a2eb2e792ea4720d045d01e803de1943e7d:4a3eb6b4988d95847d4203be25ed1d46 +``` + +For more examples, please refer to example [code-templates](https://github.com/projectdiscovery/nuclei/blob/3a5f9d626ea7b632ccca601b658acd9758f8f01b/integration_tests/protocols/code) in integration tests. + + +## FAQ + +### I got this error when running a template . What does it mean ? + +``` +./nuclei -u scanme.sh -t simple-code.yaml + + __ _ + ____ __ _______/ /__ (_) + / __ \/ / / / ___/ / _ \/ / + / / / / /_/ / /__/ / __/ / +/_/ /_/\__,_/\___/_/\___/_/ v3.0.0-dev + + projectdiscovery.io + +[WRN] Found 1 unsigned or tampered code template (carefully examine before using it & use -sign flag to sign them) +[INF] Current nuclei version: v3.0.0-dev (development) +[INF] Current nuclei-templates version: v9.6.4 (latest) +[WRN] Executing 1 unsigned templates. Use with caution. +[INF] Targets loaded for current scan: 1 +[INF] No results found. Better luck next time! +[FTL] Could not run nuclei: no templates provided for scan +``` + +Here `simple-code.yaml` is a code protocol template which is not signed or content of template has been modified after signing which indicates loss of integrity of template. +If you are template writer then you can go ahead and sign the template using `-sign` flag and if you are template consumer then you should carefully examine the template before signing it. + +### What does `re-signing code templates are not allowed for security reasons` error mean? + +```bash +nuclei -u scanme.sh -t simple-code.yaml -sign + +[ERR] could not sign 'simple-code.yaml': [signer:RUNTIME] re-signing code templates are not allowed for security reasons. +[INF] All templates signatures were elaborated success=0 failed=1 +``` + +The error message `re-signing code templates are not allowed for security reasons` comes from the Nuclei engine. This error indicates that a code template initially signed by another user and someone is trying to re-sign it. + +This measure was implemented to prevent running untrusted templates unknowingly, which might lead to potential security issues. +When you encounter this error, it suggests that you're dealing with a template that has been signed by another user Likely, the original signer is not you or the team from projectdiscovery. + +By default, Nuclei disallows executing code templates that are signed by anyone other than you or from the public templates provided by projectdiscovery/nuclei-templates. + +This is done to prevent potential security abuse using code templates. + +To resolve this error: + 1. Open and thoroughly examine the code template for any modifications. + 2. Manually remove the existing digest signature from the template. + 3. Sign the template again. + +This way, you can ensure that only templates verified and trusted by you (or projectdiscovery) are run, thus maintaining a secure environment. \ No newline at end of file diff --git a/docs/template-guide/javascript.mdx b/docs/template-guide/javascript.mdx new file mode 100644 index 00000000..bb82aa98 --- /dev/null +++ b/docs/template-guide/javascript.mdx @@ -0,0 +1,218 @@ +--- +title: "Javascript" +--- + +## Introduction + +Nuclei and its community thrives on its ability to write exploits/checks in fast and simple way in YAML format and we aim to make **nuclei templates** as standard for writing security checks and that comes with understanding its limitations and addressing them as well as expanding its capabilities. It is already possible to write most complex HTTP, DNS, SSL protocol exploits / checks with increasing support and a powerful and easy to use DSL in nuclei engine but we understand this may not be enough for addressing / writing vulnerabilities across all protocols as well as other non-remote domains of security like local privilege escalation checks, kernel etc. + +To address this and expand to other domains of security, Nuclei v3 ships with a embedded runtime for javascript that is tailored for **Nuclei** with the help of **[Goja](https://github.com/dop251/goja)**. + + +## Features + +- **Provider/Driver specific exploit** + +Some vulnerabilities are specific to software/driver, example a redis buffer overflow exploit or a exploit of specific VPN software or anything that's not a IETF standard protocol. since they are not standard protocols and it doesn't make much sense to add them as a protocol in nuclei. +Such exploits cannot be written using 'network' protocol or Very complex to write, such exploits can be written by exposing required library in nuclei (if not already present) and writing actual exploit in javascript protocol minus the boilerplate and scaling issues and other goodies of nuclei + +- **Non Network Checks** + +Security is not limited to network and nuclei also doesn't intend to limit itself to network only. There are lot of security checks that are not network related like + +1. local privilege escalation checks +2. kernel exploits +3. account misconfigurations +4. system misconfigurations etc + +- **Complex network protocol exploits** + +Some network exploits are very complex to write due to nature of protocol or exploit itself example [CVE-2020-0796](https://nvd.nist.gov/vuln/detail/cve-2020-0796) where you have to manually construct a packet. such exploits are usually written in python but now can be written in javascript protocol itself + +- **Multi Step Exploits** + +Ldap / kerberos exploits usually involves multi step process of authentication and then exploitation etc and not easy to write in YAML based DSL + +- **Scalable and maintainable exploits** + +One off exploits written in code are not scalable and maintainable due to nature of language , boilerplate code and lot of other factors. The goal here is to only write **bare minimum** code required to run exploit and let nuclei engine handle the rest + +- **Leveraging turing complete language** + +While YAML based DSL is powerful and easy to use it is not turing complete and has its own limitations. Javascript is turing complete thus users who are already familiar with javascript can write network and other exploits without learning new DSL or hacking around existing DSL. + +## Goja + +Goja is ECMAScript/Javascript engine/runtime written in pure go and has full support for ECMAScript 5.1. It is fast, can be used in goroutines and has very small memory footprint which makes it good fit for embedding in nuclei and provides additional layer of security and flexibility due to nature of javascript language and its implementation. + +This does not break any nuclei design principle nor does it change how nuclei works and is dependency free. It complements nuclei engine by adding existing turing complete language (i.e javascript) instead of re-inventing the wheel by creating new DSL (domain specific language) + +## Requirements + +- A bare minimum knowledge of javascript (loops, functions , arrays is enough) is required to write javascript protocol template +- Nuclei v3.0.0 or above + + +## API Reference + +API reference of all exposed modules and functions can be found [here](https://projectdiscovery.github.io/js-proto-docs/). + +## Javascript Protocol + +Javascript protocol is new protocol added in nuclei v3 to allow writing exploits / checks in javascript language but internally are executed in go. And this javscript is tailored towards nuclei ecosystem this means + +- It is not intended to fit / imported with any existing javascript libraries or frameworks outside of nuclei ecosystem. +- Nuclei Engine provides a set of functions, libraries that are tailor made for writing exploits / checks and only adds required/necessary functionality to compliment existing YAML based DSL. +- It is not intended to be used as general purpose javascript runtime and does not replace matchers or extractors or any existing functionality of nuclei. +- Javascript Protocol is intended to bridge gap between network protocol to add any new xyz protocol while adding lot of other functionalities. +- Nuclei v3.0.0 ships with **15+ libraries (ssh,ftp,rdp,kerberos,redis)** tailored for writing exploits/checks in javascript and will be continiously expanded in future. + +Here is a simple example of javascript protocol template + +```yaml +id: ssh-server-fingerprint + +info: + name: Fingerprint SSH Server Software + author: Ice3man543,tarunKoyalwar + severity: info + + +javascript: + - code: | + var m = require("nuclei/ssh"); + var c = m.SSHClient(); + var response = c.ConnectSSHInfoMode(Host, Port); + to_json(response); + args: + Host: "{{Host}}" + Port: "22" + + extractors: + - type: json + json: + - '.ServerID.Raw' +``` + +In above nuclei template we are fingerprinting SSH Server Software by connecting in Non-Auth mode and extracting server banner. Lets break down the template. + +### Code + +Code contains actual javascript code that is executed by nuclei engine at runtime In above template we are + +- importing `nuclei/ssh` module/library +- creating a new instance of `SSHClient` object +- connecting to SSH server in `Info` mode +- converting response to json + +### Args + +Args can be simply understood as variables in javascript that are passed at runtime and support DSL usage + + +### **Output** + +Value of Last expression is returned as output of javascript protocol template and can be used in matchers / extractors. If server returns an error instead then `error` variable is exposed in matcher/extractor with error message. + +### Example + +**SSH Password Bruteforce Template** + +```yaml +id: ssh-brute + +info: + name: SSH Credential Stuffing + author: tarunKoyalwar + severity: critical + + +javascript: + - pre-condition: | + var m = require("nuclei/ssh"); + var c = m.SSHClient(); + var response = c.ConnectSSHInfoMode(Host, Port); + // only bruteforce if ssh server allows password based authentication + response["UserAuth"].includes("password") + + code: | + var m = require("nuclei/ssh"); + var c = m.SSHClient(); + c.Connect(Host,Port,Username,Password); + + args: + Host: "{{Host}}" + Port: "22" + Username: "{{usernames}}" + Password: "{{passwords}}" + + threads: 10 + attack: clusterbomb + payloads: + usernames: helpers/wordlists/wp-users.txt + passwords: helpers/wordlists/wp-passwords.txt + + stop-at-first-match: true + matchers: + - type: dsl + dsl: + - "response == true" + - "success == true" + condition: and +``` + +In above nuclei template we are bruteforcing ssh server with list of usernames and passwords. We can tell that this might not have been possible to achieve with network template Let's break down the template. + +### Pre-Condition + +`pre-condition` is a optional javascript code that is executed before running "code" and acts as pre-condition to exploit. In above template before attempting to bruteforce we are checking if +- address is actually a ssh server +- ssh server is configured to allow password based authentication + +**Furthur explaination** + +- If pre-condition returns `true` only then code is executed otherwise it is skipped +- In code section we import `nuclei/ssh` module and create a new instance of `SSHClient` object +- and then we attempt to connect to ssh server with username and password +- this template uses [payloads](https://docs.nuclei.sh/template-guide/http/http-payloads) to launch a clusterbomb attack with 10 threads and exits on first match + +Looking at this template now we can tell that javascript template is very powerful to write multi step and protocol/vendor specific exploits which is primary goal of javascript protocol. + + +### Init + +`init` is a optional javascript code that can be used to initialize template and it is executed just after compiling template and before running it on any target. Although rarely needed, it can be used to load and preprocess data before running template on any target. +For example in below code block we are loading all ssh private keys from `nuclei-templates/helpers` directory and storing them as a variable in payloads with name `keys`, if we were loading private keys from 'pre-condition' code block then it would have been loaded for every target which is not ideal. + +``` +variables: + keysDir: "helpers/" # load all private keys from this directory + +javascript: + # init field can be used to make any preperations before the actual exploit + # here we are reading all private keys from helpers folder and storing them in a list + - init: | + let m = require('nuclei/fs'); + let privatekeys = m.ReadFilesFromDir(keysDir) + updatePayload('keys',privatekeys) + + payloads: + # 'keys' will be updated by actual private keys after init is executed + keys: + - key1 + - key2 +``` + +Two special functions that are available in init block are + + +| Function | Description | +| -------------------------- | ---------------------------------------- | +| `updatePayload(key,value)` | updates payload with given key and value | +| `set(key,value)` | sets a variable with given key and value | + +A collection of javascript protocol templates can be found [here](https://github.com/projectdiscovery/nuclei-templates/pull/8206). + +## Contributing + +If you want to add a new module or function to nuclei javascript runtime please open a PR with your changes, refer [Contributing](https://github.com/projectdiscovery/nuclei/blob/7085b47c19dbe3a70b3d4eb23177995bda5c285a/v2/pkg/js/CONTRIBUTE.md) for more details. diff --git a/docs/template-guide/workflows.mdx b/docs/template-guide/workflows.mdx index 4d4a5858..2102b407 100644 --- a/docs/template-guide/workflows.mdx +++ b/docs/template-guide/workflows.mdx @@ -14,7 +14,7 @@ Workflows can be defined with `workflows` attribute, following the `template` / ```yaml workflows: - - template: technologies/template-to-execute.yaml + - template: http/technologies/template-to-execute.yaml ``` **Type of workflows** @@ -30,10 +30,10 @@ A workflow that runs all config related templates on the list of give URLs. ```yaml workflows: - - template: files/git-config.yaml - - template: files/svn-config.yaml - - template: files/env-file.yaml - - template: files/backup-files.yaml + - template: http/exposures/configs/git-config.yaml + - template: http/exposures/configs/exposed-svn.yaml + - template: http/vulnerabilities/generic/generic-env.yaml + - template: http/exposures/backups/zip-backup-files.yaml - tags: xss,ssrf,cve,lfi ``` @@ -41,9 +41,8 @@ A workflow that runs specific list of checks defined for your project. ```yaml workflows: - - template: cves/ - - template: exposed-tokens/ - - template: exposures/ + - template: http/cves/ + - template: http/exposures/ - tags: exposures ``` ### Conditional Workflows @@ -56,7 +55,7 @@ A workflow that executes subtemplates when base template gets matched. ```yaml workflows: - - template: technologies/jira-detect.yaml + - template: http/technologies/jira-detect.yaml subtemplates: - tags: jira - template: exploits/jira/ @@ -68,7 +67,7 @@ A workflow that executes subtemplates when a matcher of base template is found i ```yaml workflows: - - template: technologies/tech-detect.yaml + - template: http/technologies/tech-detect.yaml matchers: - name: vbulletin subtemplates: @@ -89,15 +88,15 @@ A workflow showcasing chain of template executions that run only if the previous ```yaml workflows: - - template: technologies/tech-detect.yaml + - template: http/technologies/tech-detect.yaml matchers: - name: lotus-domino subtemplates: - - template: technologies/lotus-domino-version.yaml + - template: http/technologies/lotus-domino-version.yaml subtemplates: - - template: cves/xx-yy-zz.yaml + - template: http/cves/2020/xx-yy-zz.yaml subtemplates: - - template: cves/xx-xx-xx.yaml + - template: http/cves/2020/xx-xx-xx.yaml ``` Conditional workflows are great examples of performing checks and vulnerability detection in most efficient manner instead of spraying all the templates on all the targets and generally come with good ROI on your time and is gentle for the targets as well. diff --git a/integration_tests/flow/conditional-flow-negative.yaml b/integration_tests/flow/conditional-flow-negative.yaml new file mode 100644 index 00000000..d1e2cbf9 --- /dev/null +++ b/integration_tests/flow/conditional-flow-negative.yaml @@ -0,0 +1,27 @@ +id: ghost-blog-detection +info: + name: Ghost blog detection + author: pdteam + severity: info + + +flow: dns() && http() + +dns: + - name: "{{FQDN}}" + type: CNAME + + matchers: + - type: word + words: + - "ghost.io" + +http: + - method: GET + path: + - "{{BaseURL}}" + + matchers: + - type: word + words: + - "ghost.io" \ No newline at end of file diff --git a/integration_tests/flow/conditional-flow.yaml b/integration_tests/flow/conditional-flow.yaml new file mode 100644 index 00000000..d1e2cbf9 --- /dev/null +++ b/integration_tests/flow/conditional-flow.yaml @@ -0,0 +1,27 @@ +id: ghost-blog-detection +info: + name: Ghost blog detection + author: pdteam + severity: info + + +flow: dns() && http() + +dns: + - name: "{{FQDN}}" + type: CNAME + + matchers: + - type: word + words: + - "ghost.io" + +http: + - method: GET + path: + - "{{BaseURL}}" + + matchers: + - type: word + words: + - "ghost.io" \ No newline at end of file diff --git a/integration_tests/flow/dns-ns-probe.yaml b/integration_tests/flow/dns-ns-probe.yaml new file mode 100644 index 00000000..569a9e76 --- /dev/null +++ b/integration_tests/flow/dns-ns-probe.yaml @@ -0,0 +1,42 @@ +id: dns-ns-probe + +info: + name: Nuclei flow dns ns probe + author: pdteam + severity: info + description: Description of the Template + reference: https://example-reference-link + +flow: | + dns("fetch-ns"); + for(let ns of template["nameservers"]) { + set("nameserver",ns); + dns("probe-ns"); + }; + +dns: + - id: "fetch-ns" + name: "{{FQDN}}" + type: NS + matchers: + - type: word + words: + - "IN\tNS" + extractors: + - type: regex + internal: true + name: "nameservers" + group: 1 + regex: + - "IN\tNS\t(.+)" + + - id: "probe-ns" + name: "{{nameserver}}" + type: A + class: inet + retries: 3 + recursion: true + extractors: + - type: dsl + dsl: + - "a" \ No newline at end of file diff --git a/integration_tests/flow/iterate-values-flow.yaml b/integration_tests/flow/iterate-values-flow.yaml new file mode 100644 index 00000000..b92dee4a --- /dev/null +++ b/integration_tests/flow/iterate-values-flow.yaml @@ -0,0 +1,35 @@ +id: extract-emails + +info: + name: Extract Email IDs from Response + author: pdteam + severity: info + + +flow: | + http(1) + for(let email of template["emails"]) { + set("email",email); + http(2); + } + +http: + - method: GET + path: + - "{{BaseURL}}" + + extractors: + - type: regex + name: emails + internal: true + regex: + - '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' + + - method: GET + path: + - "{{BaseURL}}/user/{{base64(email)}}" + + matchers: + - type: word + words: + - "Welcome" \ No newline at end of file diff --git a/integration_tests/code/test.json b/integration_tests/library/test.json similarity index 100% rename from integration_tests/code/test.json rename to integration_tests/library/test.json diff --git a/integration_tests/code/test.yaml b/integration_tests/library/test.yaml similarity index 100% rename from integration_tests/code/test.yaml rename to integration_tests/library/test.yaml diff --git a/integration_tests/protocols/code/ps1-snippet.yaml b/integration_tests/protocols/code/ps1-snippet.yaml new file mode 100644 index 00000000..9d6c91f0 --- /dev/null +++ b/integration_tests/protocols/code/ps1-snippet.yaml @@ -0,0 +1,28 @@ +id: ps1-code-snippet + +info: + name: ps1-code-snippet + author: pdteam + severity: info + tags: code + description: | + ps1-code-snippet + +code: + - engine: + - powershell + - powershell.exe + args: + - -ExecutionPolicy + - Bypass + - -File + pattern: "*.ps1" + source: | + $stdin = [Console]::In + $line = $stdin.ReadLine() + Write-Host "hello from $line" + + matchers: + - type: word + words: + - "hello from input" \ No newline at end of file diff --git a/integration_tests/protocols/code/py-env-var.yaml b/integration_tests/protocols/code/py-env-var.yaml new file mode 100644 index 00000000..d76ca02e --- /dev/null +++ b/integration_tests/protocols/code/py-env-var.yaml @@ -0,0 +1,23 @@ +id: py-code-snippet + +info: + name: py-code-snippet + author: pdteam + severity: info + tags: code + description: | + py-code-snippet + +code: + - engine: + - py + - python3 + source: | + import sys,os + print("hello from " + sys.stdin.read() + " " + os.getenv('baz')) + + matchers: + - type: word + words: + - "hello from input baz" +# digest: 4a0a00473045022100eb01da6b97893e7868c584f330a0cd52df9bddac005860bb8595ba5b8aed58c9022050043feac68d69045cf320cba9298a2eb2e792ea4720d045d01e803de1943e7d:4a3eb6b4988d95847d4203be25ed1d46 \ No newline at end of file diff --git a/integration_tests/protocols/code/py-file.yaml b/integration_tests/protocols/code/py-file.yaml new file mode 100644 index 00000000..790daeb0 --- /dev/null +++ b/integration_tests/protocols/code/py-file.yaml @@ -0,0 +1,21 @@ +id: py-file + +info: + name: py-file + author: pdteam + severity: info + tags: code + description: | + py-file + +code: + - engine: + - py + - python3 + source: protocols/code/pyfile.py + + matchers: + - type: word + words: + - "hello from input" +# digest: 4a0a00473045022100863e07e45c5fa8d808022dfd60679145e17b4ad6c97b493ef28adaf586407dc3022001f2b2d6e565123c0ef51921862352b0b5499b4adfbf5a92af20eb77107c4920:4a3eb6b4988d95847d4203be25ed1d46 \ No newline at end of file diff --git a/integration_tests/protocols/code/py-interactsh.yaml b/integration_tests/protocols/code/py-interactsh.yaml new file mode 100644 index 00000000..173c3db5 --- /dev/null +++ b/integration_tests/protocols/code/py-interactsh.yaml @@ -0,0 +1,29 @@ +id: testcode + +info: + name: testcode + author: testcode + severity: info + tags: code + description: | + testcode + +variables: + i: "{{interactsh-url}}" + +code: + - engine: + - py + - python3 + # Simulate interactsh interaction + source: | + import os + from urllib.request import urlopen + urlopen("http://" + os.getenv('i')) + + matchers: + - type: word + part: interactsh_protocol + words: + - "http" +# digest: 4a0a004730450220785cbdcb0925c922fb34055b3b9277dec165e2f3ba938f5fd7488d400b11a1f5022100dc67027e9e8d6f249c8fc68d61866d636b137bd28e6870a716fbbe969f8b672b:4a3eb6b4988d95847d4203be25ed1d46 \ No newline at end of file diff --git a/integration_tests/protocols/code/py-nosig.yaml b/integration_tests/protocols/code/py-nosig.yaml new file mode 100644 index 00000000..d8bd0ac6 --- /dev/null +++ b/integration_tests/protocols/code/py-nosig.yaml @@ -0,0 +1,21 @@ +id: py-nosig + +info: + name: py-nosig + author: pdteam + severity: info + tags: code + description: | + Python code without signature + +code: + - engine: + - py + - python3 + source: | + print("py unsigned code") + + matchers: + - type: word + words: + - "py unsigned code" \ No newline at end of file diff --git a/integration_tests/protocols/code/py-snippet.yaml b/integration_tests/protocols/code/py-snippet.yaml new file mode 100644 index 00000000..b0e0971e --- /dev/null +++ b/integration_tests/protocols/code/py-snippet.yaml @@ -0,0 +1,24 @@ +id: py-code-snippet + +info: + name: py-code-snippet + author: pdteam + severity: info + tags: code + description: | + py-code-snippet + +code: + - engine: + - py + - python3 + - python + source: | + import sys + print("hello from " + sys.stdin.read()) + + matchers: + - type: word + words: + - "hello from input" +# digest: 4a0a00473045022067a69eb337ffa56d1c8e2cc57b7f74a5eb3294e6f366c9074778b2da3f1d795d02210096d6acda6acd2fe0ff005b08a9c0b72b63f599532ec6493f44b8518265d0e5fd:4a3eb6b4988d95847d4203be25ed1d46 \ No newline at end of file diff --git a/integration_tests/protocols/code/pyfile.py b/integration_tests/protocols/code/pyfile.py new file mode 100644 index 00000000..e318e454 --- /dev/null +++ b/integration_tests/protocols/code/pyfile.py @@ -0,0 +1,2 @@ +import sys +print("hello from " + sys.stdin.read()) \ No newline at end of file diff --git a/integration_tests/protocols/code/unsigned.yaml b/integration_tests/protocols/code/unsigned.yaml new file mode 100644 index 00000000..7e483f0e --- /dev/null +++ b/integration_tests/protocols/code/unsigned.yaml @@ -0,0 +1,21 @@ +id: unsigned-code-snippet + +info: + name: unsigned-code-snippet + author: pdteam + severity: info + tags: code + description: | + unsigned-code-snippet + +code: + - engine: + - py + - python3 + source: | + print("unsigned code") + + matchers: + - type: word + words: + - "unsigned code" \ No newline at end of file diff --git a/integration_tests/dns/basic.yaml b/integration_tests/protocols/dns/basic.yaml similarity index 100% rename from integration_tests/dns/basic.yaml rename to integration_tests/protocols/dns/basic.yaml diff --git a/integration_tests/dns/caa.yaml b/integration_tests/protocols/dns/caa.yaml similarity index 100% rename from integration_tests/dns/caa.yaml rename to integration_tests/protocols/dns/caa.yaml diff --git a/integration_tests/dns/cname-fingerprint.yaml b/integration_tests/protocols/dns/cname-fingerprint.yaml similarity index 100% rename from integration_tests/dns/cname-fingerprint.yaml rename to integration_tests/protocols/dns/cname-fingerprint.yaml diff --git a/integration_tests/dns/dsl-matcher-variable.yaml b/integration_tests/protocols/dns/dsl-matcher-variable.yaml similarity index 100% rename from integration_tests/dns/dsl-matcher-variable.yaml rename to integration_tests/protocols/dns/dsl-matcher-variable.yaml diff --git a/integration_tests/dns/payload.yaml b/integration_tests/protocols/dns/payload.yaml similarity index 100% rename from integration_tests/dns/payload.yaml rename to integration_tests/protocols/dns/payload.yaml diff --git a/integration_tests/dns/ptr.yaml b/integration_tests/protocols/dns/ptr.yaml similarity index 100% rename from integration_tests/dns/ptr.yaml rename to integration_tests/protocols/dns/ptr.yaml diff --git a/integration_tests/dns/tlsa.yaml b/integration_tests/protocols/dns/tlsa.yaml similarity index 100% rename from integration_tests/dns/tlsa.yaml rename to integration_tests/protocols/dns/tlsa.yaml diff --git a/integration_tests/dns/variables.yaml b/integration_tests/protocols/dns/variables.yaml similarity index 100% rename from integration_tests/dns/variables.yaml rename to integration_tests/protocols/dns/variables.yaml diff --git a/integration_tests/file/data/test1.txt b/integration_tests/protocols/file/data/test1.txt similarity index 100% rename from integration_tests/file/data/test1.txt rename to integration_tests/protocols/file/data/test1.txt diff --git a/integration_tests/file/data/test2.txt b/integration_tests/protocols/file/data/test2.txt similarity index 100% rename from integration_tests/file/data/test2.txt rename to integration_tests/protocols/file/data/test2.txt diff --git a/integration_tests/file/data/test3.txt b/integration_tests/protocols/file/data/test3.txt similarity index 100% rename from integration_tests/file/data/test3.txt rename to integration_tests/protocols/file/data/test3.txt diff --git a/integration_tests/file/extract.yaml b/integration_tests/protocols/file/extract.yaml similarity index 100% rename from integration_tests/file/extract.yaml rename to integration_tests/protocols/file/extract.yaml diff --git a/integration_tests/file/matcher-with-and.yaml b/integration_tests/protocols/file/matcher-with-and.yaml similarity index 100% rename from integration_tests/file/matcher-with-and.yaml rename to integration_tests/protocols/file/matcher-with-and.yaml diff --git a/integration_tests/file/matcher-with-nested-and.yaml b/integration_tests/protocols/file/matcher-with-nested-and.yaml similarity index 100% rename from integration_tests/file/matcher-with-nested-and.yaml rename to integration_tests/protocols/file/matcher-with-nested-and.yaml diff --git a/integration_tests/file/matcher-with-or.yaml b/integration_tests/protocols/file/matcher-with-or.yaml similarity index 100% rename from integration_tests/file/matcher-with-or.yaml rename to integration_tests/protocols/file/matcher-with-or.yaml diff --git a/integration_tests/headless/file-upload-negative.yaml b/integration_tests/protocols/headless/file-upload-negative.yaml similarity index 100% rename from integration_tests/headless/file-upload-negative.yaml rename to integration_tests/protocols/headless/file-upload-negative.yaml diff --git a/integration_tests/headless/file-upload.yaml b/integration_tests/protocols/headless/file-upload.yaml similarity index 85% rename from integration_tests/headless/file-upload.yaml rename to integration_tests/protocols/headless/file-upload.yaml index d339322c..e5c894f1 100644 --- a/integration_tests/headless/file-upload.yaml +++ b/integration_tests/protocols/headless/file-upload.yaml @@ -15,7 +15,7 @@ headless: args: by: xpath xpath: /html/body/form/input[1] - value: headless/file-upload.yaml + value: protocols/headless/file-upload.yaml - action: sleep args: duration: 2 diff --git a/integration_tests/headless/headless-basic.yaml b/integration_tests/protocols/headless/headless-basic.yaml similarity index 100% rename from integration_tests/headless/headless-basic.yaml rename to integration_tests/protocols/headless/headless-basic.yaml diff --git a/integration_tests/headless/headless-extract-values.yaml b/integration_tests/protocols/headless/headless-extract-values.yaml similarity index 100% rename from integration_tests/headless/headless-extract-values.yaml rename to integration_tests/protocols/headless/headless-extract-values.yaml diff --git a/integration_tests/headless/headless-header-action.yaml b/integration_tests/protocols/headless/headless-header-action.yaml similarity index 100% rename from integration_tests/headless/headless-header-action.yaml rename to integration_tests/protocols/headless/headless-header-action.yaml diff --git a/integration_tests/headless/headless-header-status-test.yaml b/integration_tests/protocols/headless/headless-header-status-test.yaml similarity index 100% rename from integration_tests/headless/headless-header-status-test.yaml rename to integration_tests/protocols/headless/headless-header-status-test.yaml diff --git a/integration_tests/headless/headless-local.yaml b/integration_tests/protocols/headless/headless-local.yaml similarity index 100% rename from integration_tests/headless/headless-local.yaml rename to integration_tests/protocols/headless/headless-local.yaml diff --git a/integration_tests/headless/headless-payloads.yaml b/integration_tests/protocols/headless/headless-payloads.yaml similarity index 100% rename from integration_tests/headless/headless-payloads.yaml rename to integration_tests/protocols/headless/headless-payloads.yaml diff --git a/integration_tests/headless/variables.yaml b/integration_tests/protocols/headless/variables.yaml similarity index 100% rename from integration_tests/headless/variables.yaml rename to integration_tests/protocols/headless/variables.yaml diff --git a/integration_tests/http/annotation-timeout.yaml b/integration_tests/protocols/http/annotation-timeout.yaml similarity index 100% rename from integration_tests/http/annotation-timeout.yaml rename to integration_tests/protocols/http/annotation-timeout.yaml diff --git a/integration_tests/http/cl-body-with-header.yaml b/integration_tests/protocols/http/cl-body-with-header.yaml similarity index 100% rename from integration_tests/http/cl-body-with-header.yaml rename to integration_tests/protocols/http/cl-body-with-header.yaml diff --git a/integration_tests/http/cl-body-without-header.yaml b/integration_tests/protocols/http/cl-body-without-header.yaml similarity index 100% rename from integration_tests/http/cl-body-without-header.yaml rename to integration_tests/protocols/http/cl-body-without-header.yaml diff --git a/integration_tests/http/cli-with-constants.yaml b/integration_tests/protocols/http/cli-with-constants.yaml similarity index 100% rename from integration_tests/http/cli-with-constants.yaml rename to integration_tests/protocols/http/cli-with-constants.yaml diff --git a/integration_tests/http/custom-attack-type.yaml b/integration_tests/protocols/http/custom-attack-type.yaml similarity index 100% rename from integration_tests/http/custom-attack-type.yaml rename to integration_tests/protocols/http/custom-attack-type.yaml diff --git a/integration_tests/http/default-matcher-condition.yaml b/integration_tests/protocols/http/default-matcher-condition.yaml similarity index 100% rename from integration_tests/http/default-matcher-condition.yaml rename to integration_tests/protocols/http/default-matcher-condition.yaml diff --git a/integration_tests/http/disable-path-automerge.yaml b/integration_tests/protocols/http/disable-path-automerge.yaml similarity index 100% rename from integration_tests/http/disable-path-automerge.yaml rename to integration_tests/protocols/http/disable-path-automerge.yaml diff --git a/integration_tests/http/disable-redirects.yaml b/integration_tests/protocols/http/disable-redirects.yaml similarity index 100% rename from integration_tests/http/disable-redirects.yaml rename to integration_tests/protocols/http/disable-redirects.yaml diff --git a/integration_tests/http/dsl-functions.yaml b/integration_tests/protocols/http/dsl-functions.yaml similarity index 100% rename from integration_tests/http/dsl-functions.yaml rename to integration_tests/protocols/http/dsl-functions.yaml diff --git a/integration_tests/http/dsl-matcher-variable.yaml b/integration_tests/protocols/http/dsl-matcher-variable.yaml similarity index 100% rename from integration_tests/http/dsl-matcher-variable.yaml rename to integration_tests/protocols/http/dsl-matcher-variable.yaml diff --git a/integration_tests/http/get-all-ips.yaml b/integration_tests/protocols/http/get-all-ips.yaml similarity index 100% rename from integration_tests/http/get-all-ips.yaml rename to integration_tests/protocols/http/get-all-ips.yaml diff --git a/integration_tests/http/get-case-insensitive.yaml b/integration_tests/protocols/http/get-case-insensitive.yaml similarity index 100% rename from integration_tests/http/get-case-insensitive.yaml rename to integration_tests/protocols/http/get-case-insensitive.yaml diff --git a/integration_tests/http/get-headers.yaml b/integration_tests/protocols/http/get-headers.yaml similarity index 100% rename from integration_tests/http/get-headers.yaml rename to integration_tests/protocols/http/get-headers.yaml diff --git a/integration_tests/http/get-host-redirects.yaml b/integration_tests/protocols/http/get-host-redirects.yaml similarity index 100% rename from integration_tests/http/get-host-redirects.yaml rename to integration_tests/protocols/http/get-host-redirects.yaml diff --git a/integration_tests/http/get-override-sni.yaml b/integration_tests/protocols/http/get-override-sni.yaml similarity index 100% rename from integration_tests/http/get-override-sni.yaml rename to integration_tests/protocols/http/get-override-sni.yaml diff --git a/integration_tests/http/get-query-string.yaml b/integration_tests/protocols/http/get-query-string.yaml similarity index 100% rename from integration_tests/http/get-query-string.yaml rename to integration_tests/protocols/http/get-query-string.yaml diff --git a/integration_tests/http/get-redirects-chain-headers.yaml b/integration_tests/protocols/http/get-redirects-chain-headers.yaml similarity index 100% rename from integration_tests/http/get-redirects-chain-headers.yaml rename to integration_tests/protocols/http/get-redirects-chain-headers.yaml diff --git a/integration_tests/http/get-redirects.yaml b/integration_tests/protocols/http/get-redirects.yaml similarity index 100% rename from integration_tests/http/get-redirects.yaml rename to integration_tests/protocols/http/get-redirects.yaml diff --git a/integration_tests/http/get-sni-unsafe.yaml b/integration_tests/protocols/http/get-sni-unsafe.yaml similarity index 100% rename from integration_tests/http/get-sni-unsafe.yaml rename to integration_tests/protocols/http/get-sni-unsafe.yaml diff --git a/integration_tests/http/get-sni.yaml b/integration_tests/protocols/http/get-sni.yaml similarity index 100% rename from integration_tests/http/get-sni.yaml rename to integration_tests/protocols/http/get-sni.yaml diff --git a/integration_tests/http/get-without-scheme.yaml b/integration_tests/protocols/http/get-without-scheme.yaml similarity index 100% rename from integration_tests/http/get-without-scheme.yaml rename to integration_tests/protocols/http/get-without-scheme.yaml diff --git a/integration_tests/http/get.yaml b/integration_tests/protocols/http/get.yaml similarity index 100% rename from integration_tests/http/get.yaml rename to integration_tests/protocols/http/get.yaml diff --git a/integration_tests/http/http-paths.yaml b/integration_tests/protocols/http/http-paths.yaml similarity index 100% rename from integration_tests/http/http-paths.yaml rename to integration_tests/protocols/http/http-paths.yaml diff --git a/integration_tests/http/interactsh-requests-mc-and.yaml b/integration_tests/protocols/http/interactsh-requests-mc-and.yaml similarity index 100% rename from integration_tests/http/interactsh-requests-mc-and.yaml rename to integration_tests/protocols/http/interactsh-requests-mc-and.yaml diff --git a/integration_tests/http/interactsh-stop-at-first-match.yaml b/integration_tests/protocols/http/interactsh-stop-at-first-match.yaml similarity index 100% rename from integration_tests/http/interactsh-stop-at-first-match.yaml rename to integration_tests/protocols/http/interactsh-stop-at-first-match.yaml diff --git a/integration_tests/http/interactsh.yaml b/integration_tests/protocols/http/interactsh.yaml similarity index 100% rename from integration_tests/http/interactsh.yaml rename to integration_tests/protocols/http/interactsh.yaml diff --git a/integration_tests/http/matcher-status.yaml b/integration_tests/protocols/http/matcher-status.yaml similarity index 97% rename from integration_tests/http/matcher-status.yaml rename to integration_tests/protocols/http/matcher-status.yaml index 5704c2a3..4cfd0d1a 100644 --- a/integration_tests/http/matcher-status.yaml +++ b/integration_tests/protocols/http/matcher-status.yaml @@ -1,4 +1,4 @@ -id: matchet-status +id: matcher-status info: name: Test Matcher Status diff --git a/integration_tests/http/post-body.yaml b/integration_tests/protocols/http/post-body.yaml similarity index 100% rename from integration_tests/http/post-body.yaml rename to integration_tests/protocols/http/post-body.yaml diff --git a/integration_tests/http/post-json-body.yaml b/integration_tests/protocols/http/post-json-body.yaml similarity index 100% rename from integration_tests/http/post-json-body.yaml rename to integration_tests/protocols/http/post-json-body.yaml diff --git a/integration_tests/http/post-multipart-body.yaml b/integration_tests/protocols/http/post-multipart-body.yaml similarity index 100% rename from integration_tests/http/post-multipart-body.yaml rename to integration_tests/protocols/http/post-multipart-body.yaml diff --git a/integration_tests/http/race-multiple.yaml b/integration_tests/protocols/http/race-multiple.yaml similarity index 100% rename from integration_tests/http/race-multiple.yaml rename to integration_tests/protocols/http/race-multiple.yaml diff --git a/integration_tests/http/race-simple.yaml b/integration_tests/protocols/http/race-simple.yaml similarity index 100% rename from integration_tests/http/race-simple.yaml rename to integration_tests/protocols/http/race-simple.yaml diff --git a/integration_tests/http/raw-cookie-reuse.yaml b/integration_tests/protocols/http/raw-cookie-reuse.yaml similarity index 100% rename from integration_tests/http/raw-cookie-reuse.yaml rename to integration_tests/protocols/http/raw-cookie-reuse.yaml diff --git a/integration_tests/http/raw-dynamic-extractor.yaml b/integration_tests/protocols/http/raw-dynamic-extractor.yaml similarity index 100% rename from integration_tests/http/raw-dynamic-extractor.yaml rename to integration_tests/protocols/http/raw-dynamic-extractor.yaml diff --git a/integration_tests/http/raw-get-query.yaml b/integration_tests/protocols/http/raw-get-query.yaml similarity index 100% rename from integration_tests/http/raw-get-query.yaml rename to integration_tests/protocols/http/raw-get-query.yaml diff --git a/integration_tests/http/raw-get.yaml b/integration_tests/protocols/http/raw-get.yaml similarity index 100% rename from integration_tests/http/raw-get.yaml rename to integration_tests/protocols/http/raw-get.yaml diff --git a/integration_tests/http/raw-path-trailing-slash.yaml b/integration_tests/protocols/http/raw-path-trailing-slash.yaml similarity index 100% rename from integration_tests/http/raw-path-trailing-slash.yaml rename to integration_tests/protocols/http/raw-path-trailing-slash.yaml diff --git a/integration_tests/http/raw-payload.yaml b/integration_tests/protocols/http/raw-payload.yaml similarity index 100% rename from integration_tests/http/raw-payload.yaml rename to integration_tests/protocols/http/raw-payload.yaml diff --git a/integration_tests/http/raw-post-body.yaml b/integration_tests/protocols/http/raw-post-body.yaml similarity index 100% rename from integration_tests/http/raw-post-body.yaml rename to integration_tests/protocols/http/raw-post-body.yaml diff --git a/integration_tests/http/raw-unsafe-path.yaml b/integration_tests/protocols/http/raw-unsafe-path.yaml similarity index 100% rename from integration_tests/http/raw-unsafe-path.yaml rename to integration_tests/protocols/http/raw-unsafe-path.yaml diff --git a/integration_tests/http/raw-unsafe-request.yaml b/integration_tests/protocols/http/raw-unsafe-request.yaml similarity index 100% rename from integration_tests/http/raw-unsafe-request.yaml rename to integration_tests/protocols/http/raw-unsafe-request.yaml diff --git a/integration_tests/http/raw-unsafe-with-params.yaml b/integration_tests/protocols/http/raw-unsafe-with-params.yaml similarity index 100% rename from integration_tests/http/raw-unsafe-with-params.yaml rename to integration_tests/protocols/http/raw-unsafe-with-params.yaml diff --git a/integration_tests/http/raw-with-params.yaml b/integration_tests/protocols/http/raw-with-params.yaml similarity index 100% rename from integration_tests/http/raw-with-params.yaml rename to integration_tests/protocols/http/raw-with-params.yaml diff --git a/integration_tests/http/redirect-match-url.yaml b/integration_tests/protocols/http/redirect-match-url.yaml similarity index 100% rename from integration_tests/http/redirect-match-url.yaml rename to integration_tests/protocols/http/redirect-match-url.yaml diff --git a/integration_tests/http/request-condition-new.yaml b/integration_tests/protocols/http/request-condition-new.yaml similarity index 100% rename from integration_tests/http/request-condition-new.yaml rename to integration_tests/protocols/http/request-condition-new.yaml diff --git a/integration_tests/http/request-condition.yaml b/integration_tests/protocols/http/request-condition.yaml similarity index 100% rename from integration_tests/http/request-condition.yaml rename to integration_tests/protocols/http/request-condition.yaml diff --git a/integration_tests/http/save-extractor-values-to-file.yaml b/integration_tests/protocols/http/save-extractor-values-to-file.yaml similarity index 100% rename from integration_tests/http/save-extractor-values-to-file.yaml rename to integration_tests/protocols/http/save-extractor-values-to-file.yaml diff --git a/integration_tests/http/self-contained-file-input.yaml b/integration_tests/protocols/http/self-contained-file-input.yaml similarity index 100% rename from integration_tests/http/self-contained-file-input.yaml rename to integration_tests/protocols/http/self-contained-file-input.yaml diff --git a/integration_tests/http/self-contained-with-params.yaml b/integration_tests/protocols/http/self-contained-with-params.yaml similarity index 100% rename from integration_tests/http/self-contained-with-params.yaml rename to integration_tests/protocols/http/self-contained-with-params.yaml diff --git a/integration_tests/http/self-contained-with-path.yaml b/integration_tests/protocols/http/self-contained-with-path.yaml similarity index 100% rename from integration_tests/http/self-contained-with-path.yaml rename to integration_tests/protocols/http/self-contained-with-path.yaml diff --git a/integration_tests/http/self-contained.yaml b/integration_tests/protocols/http/self-contained.yaml similarity index 100% rename from integration_tests/http/self-contained.yaml rename to integration_tests/protocols/http/self-contained.yaml diff --git a/integration_tests/http/stop-at-first-match-with-extractors.yaml b/integration_tests/protocols/http/stop-at-first-match-with-extractors.yaml similarity index 100% rename from integration_tests/http/stop-at-first-match-with-extractors.yaml rename to integration_tests/protocols/http/stop-at-first-match-with-extractors.yaml diff --git a/integration_tests/http/stop-at-first-match.yaml b/integration_tests/protocols/http/stop-at-first-match.yaml similarity index 100% rename from integration_tests/http/stop-at-first-match.yaml rename to integration_tests/protocols/http/stop-at-first-match.yaml diff --git a/integration_tests/http/variable-dsl-function.yaml b/integration_tests/protocols/http/variable-dsl-function.yaml similarity index 100% rename from integration_tests/http/variable-dsl-function.yaml rename to integration_tests/protocols/http/variable-dsl-function.yaml diff --git a/integration_tests/http/variables.yaml b/integration_tests/protocols/http/variables.yaml similarity index 100% rename from integration_tests/http/variables.yaml rename to integration_tests/protocols/http/variables.yaml diff --git a/integration_tests/protocols/javascript/net-multi-step.yaml b/integration_tests/protocols/javascript/net-multi-step.yaml new file mode 100644 index 00000000..0d638fa5 --- /dev/null +++ b/integration_tests/protocols/javascript/net-multi-step.yaml @@ -0,0 +1,32 @@ +id: network-multi-step +info: + name: network multi-step + author: tarunKoyalwar + severity: high + description: | + Network multi-step template for testing + + +javascript: + - code: | + var m = require("nuclei/net"); + var conn = m.Open("tcp",address); + conn.SetTimeout(timeout); // optional timeout + conn.Send("FIRST") + conn.RecvString(4) // READ 4 bytes i.e PING + conn.Send("SECOND") + conn.RecvString(4) // READ 4 bytes i.e PONG + conn.RecvString(6) // READ 6 bytes i.e NUCLEI + + args: + address: "{{Host}}:{{Port}}" + Host: "{{Host}}" + Port: 5431 + timeout: 3 # in sec + + matchers: + - type: dsl + dsl: + - success == true + - response == "NUCLEI" + condition: and diff --git a/integration_tests/protocols/javascript/redis-pass-brute.yaml b/integration_tests/protocols/javascript/redis-pass-brute.yaml new file mode 100644 index 00000000..d4d09230 --- /dev/null +++ b/integration_tests/protocols/javascript/redis-pass-brute.yaml @@ -0,0 +1,43 @@ +id: redis-pass-brute +info: + name: redis password bruteforce + author: tarunKoyalwar + severity: high + description: | + This template bruteforces passwords for protected redis instances. + If redis is not protected with password. it is also matched + metadata: + shodan-query: product:"redis" + + +javascript: + - pre-condition: | + isPortOpen(Host,Port) + + code: | + var m = require("nuclei/redis"); + m.GetServerInfoAuth(Host,Port,Password); + + args: + Host: "{{Host}}" + Port: "6379" + Password: "{{passwords}}" + + payloads: + passwords: + - "" + - root + - password + - admin + - iamadmin + stop-at-first-match: true + + matchers-condition: and + matchers: + - type: word + words: + - "redis_version" + - type: word + negative: true + words: + - "redis_mode:sentinel" diff --git a/integration_tests/protocols/javascript/ssh-server-fingerprint.yaml b/integration_tests/protocols/javascript/ssh-server-fingerprint.yaml new file mode 100644 index 00000000..20d47277 --- /dev/null +++ b/integration_tests/protocols/javascript/ssh-server-fingerprint.yaml @@ -0,0 +1,26 @@ +id: ssh-server-fingerprint + +info: + name: Fingerprint SSH Server Software + author: Ice3man543,tarunKoyalwar + severity: info + metadata: + shodan-query: port:22 + + +javascript: + - code: | + var m = require("nuclei/ssh"); + var c = m.SSHClient(); + var response = c.ConnectSSHInfoMode(Host, Port); + to_json(response); + args: + Host: "{{Host}}" + Port: "22" + + extractors: + - type: json + name: server + json: + - '.ServerID.Raw' + part: response diff --git a/integration_tests/protocols/keys/ci-private-key.pem b/integration_tests/protocols/keys/ci-private-key.pem new file mode 100644 index 00000000..6890d5f7 --- /dev/null +++ b/integration_tests/protocols/keys/ci-private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PD NUCLEI USER PRIVATE KEY----- +MHcCAQEEIEywlBGZ94ARrBT+1fTu/Ii7HGfJc4y7kK4aGYvDMYm5oAoGCCqGSM49 +AwEHoUQDQgAEnyVUkFKJx92/8doQ//VAPCrzB4dqvNgwLRZPC/oAieVpNG8HDGNw +PJ7qB7ovIfGwDOW98vQwsRG4TmgFlZr0rQ== +-----END PD NUCLEI USER PRIVATE KEY----- diff --git a/integration_tests/protocols/keys/ci.crt b/integration_tests/protocols/keys/ci.crt new file mode 100644 index 00000000..bef501ad --- /dev/null +++ b/integration_tests/protocols/keys/ci.crt @@ -0,0 +1,9 @@ +-----BEGIN PD NUCLEI USER CERTIFICATE----- +MIIBPzCB56ADAgECAgRlHGgmMAoGCCqGSM49BAMCMA0xCzAJBgNVBAMTAkNJMB4X +DTIzMTAwMzE5MTQ0NloXDTI3MTAwMjE5MTQ0NlowDTELMAkGA1UEAxMCQ0kwWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAASfJVSQUonH3b/x2hD/9UA8KvMHh2q82DAt +Fk8L+gCJ5Wk0bwcMY3A8nuoHui8h8bAM5b3y9DCxEbhOaAWVmvStozUwMzAOBgNV +HQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAK +BggqhkjOPQQDAgNHADBEAiBgUdbAcSbDpkNNQscZog/pAuaRV4sk7fbOlTRcjZTL +qQIgdtvG1w7l9VAtk6gx+HJa3BP9IFhSfT+a3UCuJy2p2iA= +-----END PD NUCLEI USER CERTIFICATE----- diff --git a/integration_tests/protocols/multi/dynamic-values.yaml b/integration_tests/protocols/multi/dynamic-values.yaml new file mode 100644 index 00000000..12f429ce --- /dev/null +++ b/integration_tests/protocols/multi/dynamic-values.yaml @@ -0,0 +1,29 @@ +id: dns-http-dynamic-values + +info: + name: multi protocol request with dynamic values + author: pdteam + severity: info + +dns: + - name: "{{FQDN}}" # DNS Request + type: cname + + extractors: + - type: dsl + name: blogid + dsl: + - trim_suffix(cname,'.ghost.io.') + internal: true + +http: + - method: GET # http request + path: + - "{{BaseURL}}" + + matchers: + - type: dsl + dsl: + - contains(body,'ProjectDiscovery.io') # check for http string + - blogid == 'projectdiscovery' # check for cname (extracted information from dns response) + condition: and \ No newline at end of file diff --git a/integration_tests/protocols/multi/evaluate-variables.yaml b/integration_tests/protocols/multi/evaluate-variables.yaml new file mode 100644 index 00000000..49b22385 --- /dev/null +++ b/integration_tests/protocols/multi/evaluate-variables.yaml @@ -0,0 +1,30 @@ +id: dns-ssl-http-with-variables + +info: + name: multi protocol request with dynamic values + author: pdteam + severity: info + + +variables: + cname_filtered: '{{trim_suffix(dns_cname,".ghost.io.")}}' + +dns: + - name: "{{FQDN}}" # DNS Request + type: cname + +ssl: + - address: "{{Hostname}}" # ssl request + +http: + - method: GET # http request + path: + - "{{BaseURL}}" + + matchers: + - type: dsl + dsl: + - contains(http_body,'ProjectDiscovery.io') # check for http string + - cname_filtered == 'projectdiscovery' # check for cname (extracted information from dns response) + - ssl_subject_cn == 'blog.projectdiscovery.io' + condition: and \ No newline at end of file diff --git a/integration_tests/protocols/multi/exported-response-vars.yaml b/integration_tests/protocols/multi/exported-response-vars.yaml new file mode 100644 index 00000000..b6ba3ea2 --- /dev/null +++ b/integration_tests/protocols/multi/exported-response-vars.yaml @@ -0,0 +1,26 @@ +id: dns-ssl-http-proto-prefix + +info: + name: multi protocol request with dynamic values + author: pdteam + severity: info + +dns: + - name: "{{FQDN}}" # DNS Request + type: cname + +ssl: + - address: "{{Hostname}}" # ssl request + +http: + - method: GET # http request + path: + - "{{BaseURL}}" + + matchers: + - type: dsl + dsl: + - contains(http_body,'ProjectDiscovery.io') # check for http string + - trim_suffix(dns_cname,'.ghost.io.') == 'projectdiscovery' # check for cname (extracted information from dns response) + - ssl_subject_cn == 'blog.projectdiscovery.io' + condition: and \ No newline at end of file diff --git a/integration_tests/network/basic.yaml b/integration_tests/protocols/network/basic.yaml similarity index 100% rename from integration_tests/network/basic.yaml rename to integration_tests/protocols/network/basic.yaml diff --git a/integration_tests/network/hex.yaml b/integration_tests/protocols/network/hex.yaml similarity index 100% rename from integration_tests/network/hex.yaml rename to integration_tests/protocols/network/hex.yaml diff --git a/integration_tests/network/multi-step.yaml b/integration_tests/protocols/network/multi-step.yaml similarity index 100% rename from integration_tests/network/multi-step.yaml rename to integration_tests/protocols/network/multi-step.yaml diff --git a/integration_tests/network/network-port.yaml b/integration_tests/protocols/network/network-port.yaml similarity index 100% rename from integration_tests/network/network-port.yaml rename to integration_tests/protocols/network/network-port.yaml diff --git a/integration_tests/network/same-address.yaml b/integration_tests/protocols/network/same-address.yaml similarity index 100% rename from integration_tests/network/same-address.yaml rename to integration_tests/protocols/network/same-address.yaml diff --git a/integration_tests/network/self-contained.yaml b/integration_tests/protocols/network/self-contained.yaml similarity index 100% rename from integration_tests/network/self-contained.yaml rename to integration_tests/protocols/network/self-contained.yaml diff --git a/integration_tests/network/variables.yaml b/integration_tests/protocols/network/variables.yaml similarity index 100% rename from integration_tests/network/variables.yaml rename to integration_tests/protocols/network/variables.yaml diff --git a/integration_tests/offlinehttp/data/req-resp-with-http-keywords.txt b/integration_tests/protocols/offlinehttp/data/req-resp-with-http-keywords.txt similarity index 100% rename from integration_tests/offlinehttp/data/req-resp-with-http-keywords.txt rename to integration_tests/protocols/offlinehttp/data/req-resp-with-http-keywords.txt diff --git a/integration_tests/offlinehttp/offline-allowed-paths.yaml b/integration_tests/protocols/offlinehttp/offline-allowed-paths.yaml similarity index 100% rename from integration_tests/offlinehttp/offline-allowed-paths.yaml rename to integration_tests/protocols/offlinehttp/offline-allowed-paths.yaml diff --git a/integration_tests/offlinehttp/offline-raw.yaml b/integration_tests/protocols/offlinehttp/offline-raw.yaml similarity index 100% rename from integration_tests/offlinehttp/offline-raw.yaml rename to integration_tests/protocols/offlinehttp/offline-raw.yaml diff --git a/integration_tests/offlinehttp/rfc-req-resp.yaml b/integration_tests/protocols/offlinehttp/rfc-req-resp.yaml similarity index 100% rename from integration_tests/offlinehttp/rfc-req-resp.yaml rename to integration_tests/protocols/offlinehttp/rfc-req-resp.yaml diff --git a/integration_tests/ssl/basic-ztls.yaml b/integration_tests/protocols/ssl/basic-ztls.yaml similarity index 100% rename from integration_tests/ssl/basic-ztls.yaml rename to integration_tests/protocols/ssl/basic-ztls.yaml diff --git a/integration_tests/ssl/basic.yaml b/integration_tests/protocols/ssl/basic.yaml similarity index 100% rename from integration_tests/ssl/basic.yaml rename to integration_tests/protocols/ssl/basic.yaml diff --git a/integration_tests/ssl/custom-cipher.yaml b/integration_tests/protocols/ssl/custom-cipher.yaml similarity index 100% rename from integration_tests/ssl/custom-cipher.yaml rename to integration_tests/protocols/ssl/custom-cipher.yaml diff --git a/integration_tests/ssl/custom-version.yaml b/integration_tests/protocols/ssl/custom-version.yaml similarity index 100% rename from integration_tests/ssl/custom-version.yaml rename to integration_tests/protocols/ssl/custom-version.yaml diff --git a/integration_tests/ssl/ssl-with-vars.yaml b/integration_tests/protocols/ssl/ssl-with-vars.yaml similarity index 100% rename from integration_tests/ssl/ssl-with-vars.yaml rename to integration_tests/protocols/ssl/ssl-with-vars.yaml diff --git a/integration_tests/websocket/basic.yaml b/integration_tests/protocols/websocket/basic.yaml similarity index 100% rename from integration_tests/websocket/basic.yaml rename to integration_tests/protocols/websocket/basic.yaml diff --git a/integration_tests/websocket/cswsh.yaml b/integration_tests/protocols/websocket/cswsh.yaml similarity index 100% rename from integration_tests/websocket/cswsh.yaml rename to integration_tests/protocols/websocket/cswsh.yaml diff --git a/integration_tests/websocket/no-cswsh.yaml b/integration_tests/protocols/websocket/no-cswsh.yaml similarity index 100% rename from integration_tests/websocket/no-cswsh.yaml rename to integration_tests/protocols/websocket/no-cswsh.yaml diff --git a/integration_tests/websocket/path.yaml b/integration_tests/protocols/websocket/path.yaml similarity index 100% rename from integration_tests/websocket/path.yaml rename to integration_tests/protocols/websocket/path.yaml diff --git a/integration_tests/whois/basic.yaml b/integration_tests/protocols/whois/basic.yaml similarity index 100% rename from integration_tests/whois/basic.yaml rename to integration_tests/protocols/whois/basic.yaml diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json index d5041df8..f9def413 100644 --- a/nuclei-jsonschema.json +++ b/nuclei-jsonschema.json @@ -741,19 +741,6 @@ "type": "string", "title": "condition between the matchers", "description": "Conditions between the matchers" - }, - "fuzzing": { - "items": { - "$ref": "#/definitions/fuzz.Rule" - }, - "type": "array", - "title": "fuzzin rules for http fuzzing", - "description": "Fuzzing describes rule schema to fuzz headless requests" - }, - "cookie-reuse": { - "type": "boolean", - "title": "optional cookie reuse enable", - "description": "Optional setting that enables cookie reuse" } }, "additionalProperties": false, diff --git a/v2/Makefile b/v2/Makefile index e08efb52..966bdefe 100644 --- a/v2/Makefile +++ b/v2/Makefile @@ -30,3 +30,8 @@ functional: cd cmd/functional-test; bash run.sh tidy: $(GOMOD) tidy +devtools: + $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "bindgen" pkg/js/devtools/bindgen/cmd/bindgen/main.go + $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "jsdocgen" pkg/js/devtools/jsdocgen/main.go + $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scrapefuncs" pkg/js/devtools/scrapefuncs/main.go + diff --git a/v2/cmd/integration-test/code.go b/v2/cmd/integration-test/code.go index b2b5f94c..63362fbc 100644 --- a/v2/cmd/integration-test/code.go +++ b/v2/cmd/integration-test/code.go @@ -1,134 +1,140 @@ package main import ( - "context" - "fmt" + "errors" "log" - "net/http" - "net/http/httptest" - "os" - "path" - "strings" - "time" + "path/filepath" - "github.com/julienschmidt/httprouter" - "github.com/logrusorgru/aurora" - "github.com/pkg/errors" - "github.com/projectdiscovery/goflags" - "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" - "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" - "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader" - "github.com/projectdiscovery/nuclei/v2/pkg/core" - "github.com/projectdiscovery/nuclei/v2/pkg/core/inputs" - "github.com/projectdiscovery/nuclei/v2/pkg/output" - "github.com/projectdiscovery/nuclei/v2/pkg/parsers" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" - "github.com/projectdiscovery/nuclei/v2/pkg/reporting" + osutils "github.com/projectdiscovery/utils/os" + + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" "github.com/projectdiscovery/nuclei/v2/pkg/testutils" - "github.com/projectdiscovery/nuclei/v2/pkg/types" - "github.com/projectdiscovery/ratelimit" ) -var codeTestcases = []TestCaseInfo{ - {Path: "code/test.yaml", TestCase: &goIntegrationTest{}}, - {Path: "code/test.json", TestCase: &goIntegrationTest{}}, +var codeTestCases = []TestCaseInfo{ + {Path: "protocols/code/py-snippet.yaml", TestCase: &codeSnippet{}}, + {Path: "protocols/code/py-file.yaml", TestCase: &codeFile{}}, + {Path: "protocols/code/py-env-var.yaml", TestCase: &codeEnvVar{}}, + {Path: "protocols/code/unsigned.yaml", TestCase: &unsignedCode{}}, + {Path: "protocols/code/py-nosig.yaml", TestCase: &codePyNoSig{}}, + {Path: "protocols/code/py-interactsh.yaml", TestCase: &codeSnippet{}}, + {Path: "protocols/code/ps1-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() }}, } -type goIntegrationTest struct{} +const ( + testCertFile = "protocols/keys/ci.crt" + testKeyFile = "protocols/keys/ci-private-key.pem" +) + +var testcertpath = "" + +func init() { + // allow local file access to load content of file references in template + // in order to sign them for testing purposes + templates.TemplateSignerLFA() + + tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile) + if err != nil { + panic(err) + } + + testcertpath, _ = filepath.Abs(testCertFile) + + for _, v := range codeTestCases { + templatePath := v.Path + testCase := v.TestCase + + if v.DisableOn != nil && v.DisableOn() { + // skip ps1 test case on non-windows platforms + continue + } + + templatePath, err := filepath.Abs(templatePath) + if err != nil { + panic(err) + } + + // skip + // - unsigned test cases + if _, ok := testCase.(*unsignedCode); ok { + continue + } + if _, ok := testCase.(*codePyNoSig); ok { + continue + } + if err := templates.SignTemplate(tsigner, templatePath); err != nil { + log.Fatalf("Could not sign template %v got: %s\n", templatePath, err) + } + } + +} + +func getEnvValues() []string { + return []string{ + signer.CertEnvVarName + "=" + testcertpath, + } +} + +type codeSnippet struct{} // Execute executes a test case and returns an error if occurred -// -// Execute the docs at ../DESIGN.md if the code stops working for integration. -func (h *goIntegrationTest) Execute(templatePath string) error { - router := httprouter.New() - - router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") - if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") - } - }) - ts := httptest.NewServer(router) - defer ts.Close() - - results, err := executeNucleiAsCode(templatePath, ts.URL) +func (h *codeSnippet) Execute(filePath string) error { + results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input") if err != nil { return err } return expectResultsCount(results, 1) } -// executeNucleiAsCode contains an example -func executeNucleiAsCode(templatePath, templateURL string) ([]string, error) { - cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil) - defer cache.Close() +type codeFile struct{} - mockProgress := &testutils.MockProgressClient{} - reportingClient, err := reporting.New(&reporting.Options{}, "") +// Execute executes a test case and returns an error if occurred +func (h *codeFile) Execute(filePath string) error { + results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input") if err != nil { - return nil, err + return err } - defer reportingClient.Close() - - outputWriter := testutils.NewMockOutputWriter() - var results []string - outputWriter.WriteCallback = func(event *output.ResultEvent) { - results = append(results, fmt.Sprintf("%v\n", event)) - } - - defaultOpts := types.DefaultOptions() - _ = protocolstate.Init(defaultOpts) - _ = protocolinit.Init(defaultOpts) - - defaultOpts.Templates = goflags.StringSlice{templatePath} - defaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags - - interactOpts := interactsh.DefaultOptions(outputWriter, reportingClient, mockProgress) - interactClient, err := interactsh.New(interactOpts) - if err != nil { - return nil, errors.Wrap(err, "could not create interact client") - } - defer interactClient.Close() - - home, _ := os.UserHomeDir() - catalog := disk.NewCatalog(path.Join(home, "nuclei-templates")) - ratelimiter := ratelimit.New(context.Background(), 150, time.Second) - defer ratelimiter.Stop() - executerOpts := protocols.ExecutorOptions{ - Output: outputWriter, - Options: defaultOpts, - Progress: mockProgress, - Catalog: catalog, - IssuesClient: reportingClient, - RateLimiter: ratelimiter, - Interactsh: interactClient, - HostErrorsCache: cache, - Colorizer: aurora.NewAurora(true), - ResumeCfg: types.NewResumeCfg(), - } - engine := core.New(defaultOpts) - engine.SetExecuterOptions(executerOpts) - - workflowLoader, err := parsers.NewLoader(&executerOpts) - if err != nil { - log.Fatalf("Could not create workflow loader: %s\n", err) - } - executerOpts.WorkflowLoader = workflowLoader - - store, err := loader.New(loader.NewConfig(defaultOpts, catalog, executerOpts)) - if err != nil { - return nil, errors.Wrap(err, "could not create loader") - } - store.Load() - - input := &inputs.SimpleInputProvider{Inputs: []*contextargs.MetaInput{{Input: templateURL}}} - _ = engine.Execute(store.Templates(), input) - engine.WorkPool().Wait() // Wait for the scan to finish - - return results, nil + return expectResultsCount(results, 1) +} + +type codeEnvVar struct{} + +// Execute executes a test case and returns an error if occurred +func (h *codeEnvVar) Execute(filePath string) error { + results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-V", "baz=baz") + if err != nil { + return err + } + return expectResultsCount(results, 1) +} + +type unsignedCode struct{} + +// Execute executes a test case and returns an error if occurred +func (h *unsignedCode) Execute(filePath string) error { + results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input") + + // should error out + if err != nil { + return nil + } + + // this point should never be reached + return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed")) +} + +type codePyNoSig struct{} + +// Execute executes a test case and returns an error if occurred +func (h *codePyNoSig) Execute(filePath string) error { + results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input") + + // should error out + if err != nil { + return nil + } + + // this point should never be reached + return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed")) } diff --git a/v2/cmd/integration-test/custom-dir.go b/v2/cmd/integration-test/custom-dir.go index 774a367d..7c6f8cc1 100644 --- a/v2/cmd/integration-test/custom-dir.go +++ b/v2/cmd/integration-test/custom-dir.go @@ -9,7 +9,7 @@ import ( type customConfigDirTest struct{} var customConfigDirTestCases = []TestCaseInfo{ - {Path: "dns/cname-fingerprint.yaml", TestCase: &customConfigDirTest{}}, + {Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &customConfigDirTest{}}, } // Execute executes a test case and returns an error if occurred @@ -19,7 +19,7 @@ func (h *customConfigDirTest) Execute(filePath string) error { return err } defer os.RemoveAll(customTempDirectory) - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug, "-config-directory", customTempDirectory) + results, err := testutils.RunNucleiBareArgsAndGetResults(debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDirectory}, "-t", filePath, "-u", "8x8exch02.8x8.com") if err != nil { return err } diff --git a/v2/cmd/integration-test/dns.go b/v2/cmd/integration-test/dns.go index 570f734a..d0035e8a 100644 --- a/v2/cmd/integration-test/dns.go +++ b/v2/cmd/integration-test/dns.go @@ -5,13 +5,13 @@ import ( ) var dnsTestCases = []TestCaseInfo{ - {Path: "dns/basic.yaml", TestCase: &dnsBasic{}}, - {Path: "dns/ptr.yaml", TestCase: &dnsPtr{}}, - {Path: "dns/caa.yaml", TestCase: &dnsCAA{}}, - {Path: "dns/tlsa.yaml", TestCase: &dnsTLSA{}}, - {Path: "dns/variables.yaml", TestCase: &dnsVariables{}}, - {Path: "dns/payload.yaml", TestCase: &dnsPayload{}}, - {Path: "dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}}, + {Path: "protocols/dns/basic.yaml", TestCase: &dnsBasic{}}, + {Path: "protocols/dns/ptr.yaml", TestCase: &dnsPtr{}}, + {Path: "protocols/dns/caa.yaml", TestCase: &dnsCAA{}}, + {Path: "protocols/dns/tlsa.yaml", TestCase: &dnsTLSA{}}, + {Path: "protocols/dns/variables.yaml", TestCase: &dnsVariables{}}, + {Path: "protocols/dns/payload.yaml", TestCase: &dnsPayload{}}, + {Path: "protocols/dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}}, } type dnsBasic struct{} diff --git a/v2/cmd/integration-test/dsl.go b/v2/cmd/integration-test/dsl.go index 76c6dc0f..6a1666d7 100644 --- a/v2/cmd/integration-test/dsl.go +++ b/v2/cmd/integration-test/dsl.go @@ -14,6 +14,8 @@ var dslTestcases = []TestCaseInfo{ {Path: "dsl/show-version-warning.yaml", TestCase: &dslShowVersionWarning{}}, } +var defaultDSLEnvs = []string{"HIDE_TEMPLATE_SIG_WARNING=true"} + type dslVersionWarning struct{} func (d *dslVersionWarning) Execute(templatePath string) error { @@ -23,7 +25,7 @@ func (d *dslVersionWarning) Execute(templatePath string) error { }) ts := httptest.NewServer(router) defer ts.Close() - results, err := testutils.RunNucleiArgsAndGetErrors(debug, nil, "-t", templatePath, "-target", ts.URL, "-v") + results, err := testutils.RunNucleiArgsAndGetErrors(debug, defaultDSLEnvs, "-t", templatePath, "-target", ts.URL, "-v") if err != nil { return err } @@ -39,7 +41,7 @@ func (d *dslShowVersionWarning) Execute(templatePath string) error { }) ts := httptest.NewServer(router) defer ts.Close() - results, err := testutils.RunNucleiArgsAndGetErrors(debug, []string{"SHOW_DSL_ERRORS=true"}, "-t", templatePath, "-target", ts.URL, "-v") + results, err := testutils.RunNucleiArgsAndGetErrors(debug, append(defaultDSLEnvs, "SHOW_DSL_ERRORS=true"), "-t", templatePath, "-target", ts.URL, "-v") if err != nil { return err } diff --git a/v2/cmd/integration-test/file.go b/v2/cmd/integration-test/file.go index c5351f30..c7c4b669 100644 --- a/v2/cmd/integration-test/file.go +++ b/v2/cmd/integration-test/file.go @@ -5,17 +5,17 @@ import ( ) var fileTestcases = []TestCaseInfo{ - {Path: "file/matcher-with-or.yaml", TestCase: &fileWithOrMatcher{}}, - {Path: "file/matcher-with-and.yaml", TestCase: &fileWithAndMatcher{}}, - {Path: "file/matcher-with-nested-and.yaml", TestCase: &fileWithAndMatcher{}}, - {Path: "file/extract.yaml", TestCase: &fileWithExtractor{}}, + {Path: "protocols/file/matcher-with-or.yaml", TestCase: &fileWithOrMatcher{}}, + {Path: "protocols/file/matcher-with-and.yaml", TestCase: &fileWithAndMatcher{}}, + {Path: "protocols/file/matcher-with-nested-and.yaml", TestCase: &fileWithAndMatcher{}}, + {Path: "protocols/file/extract.yaml", TestCase: &fileWithExtractor{}}, } type fileWithOrMatcher struct{} // Execute executes a test case and returns an error if occurred func (h *fileWithOrMatcher) Execute(filePath string) error { - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "file/data/", debug) + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug) if err != nil { return err } @@ -27,7 +27,7 @@ type fileWithAndMatcher struct{} // Execute executes a test case and returns an error if occurred func (h *fileWithAndMatcher) Execute(filePath string) error { - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "file/data/", debug) + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug) if err != nil { return err } @@ -39,7 +39,7 @@ type fileWithExtractor struct{} // Execute executes a test case and returns an error if occurred func (h *fileWithExtractor) Execute(filePath string) error { - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "file/data/", debug) + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug) if err != nil { return err } diff --git a/v2/cmd/integration-test/flow.go b/v2/cmd/integration-test/flow.go new file mode 100644 index 00000000..67d4b749 --- /dev/null +++ b/v2/cmd/integration-test/flow.go @@ -0,0 +1,84 @@ +package main + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/http/httptest" + + "github.com/julienschmidt/httprouter" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" +) + +var flowTestcases = []TestCaseInfo{ + {Path: "flow/conditional-flow.yaml", TestCase: &conditionalFlow{}}, + {Path: "flow/conditional-flow-negative.yaml", TestCase: &conditionalFlowNegative{}}, + {Path: "flow/iterate-values-flow.yaml", TestCase: &iterateValuesFlow{}}, + {Path: "flow/dns-ns-probe.yaml", TestCase: &dnsNsProbe{}}, +} + +type conditionalFlow struct{} + +func (t *conditionalFlow) Execute(filePath string) error { + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "blog.projectdiscovery.io", debug) + if err != nil { + return err + } + return expectResultsCount(results, 2) +} + +type conditionalFlowNegative struct{} + +func (t *conditionalFlowNegative) Execute(filePath string) error { + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug) + if err != nil { + return err + } + return expectResultsCount(results, 0) +} + +type iterateValuesFlow struct{} + +func (t *iterateValuesFlow) Execute(filePath string) error { + router := httprouter.New() + testemails := []string{ + "secrets@scanme.sh", + "superadmin@scanme.sh", + } + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(fmt.Sprint(testemails))) + }) + router.GET("/user/"+getBase64(testemails[0]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Welcome ! This is test matcher text")) + }) + + router.GET("/user/"+getBase64(testemails[1]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Welcome ! This is test matcher text")) + }) + + ts := httptest.NewServer(router) + defer ts.Close() + + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug) + if err != nil { + return err + } + return expectResultsCount(results, 2) +} + +type dnsNsProbe struct{} + +func (t *dnsNsProbe) Execute(filePath string) error { + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "oast.fun", debug) + if err != nil { + return err + } + return expectResultsCount(results, 3) +} + +func getBase64(input string) string { + return base64.StdEncoding.EncodeToString([]byte(input)) +} diff --git a/v2/cmd/integration-test/headless.go b/v2/cmd/integration-test/headless.go index b30c464c..3ae57a93 100644 --- a/v2/cmd/integration-test/headless.go +++ b/v2/cmd/integration-test/headless.go @@ -11,15 +11,15 @@ import ( ) var headlessTestcases = []TestCaseInfo{ - {Path: "headless/headless-basic.yaml", TestCase: &headlessBasic{}}, - {Path: "headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}}, - {Path: "headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}}, - {Path: "headless/headless-payloads.yaml", TestCase: &headlessPayloads{}}, - {Path: "headless/variables.yaml", TestCase: &headlessVariables{}}, - {Path: "headless/headless-local.yaml", TestCase: &headlessLocal{}}, - {Path: "headless/file-upload.yaml", TestCase: &headlessFileUpload{}}, - {Path: "headless/file-upload-negative.yaml", TestCase: &headlessFileUploadNegative{}}, - {Path: "headless/headless-header-status-test.yaml", TestCase: &headlessHeaderStatus{}}, + {Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}}, + {Path: "protocols/headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}}, + {Path: "protocols/headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}}, + {Path: "protocols/headless/headless-payloads.yaml", TestCase: &headlessPayloads{}}, + {Path: "protocols/headless/variables.yaml", TestCase: &headlessVariables{}}, + {Path: "protocols/headless/headless-local.yaml", TestCase: &headlessLocal{}}, + {Path: "protocols/headless/file-upload.yaml", TestCase: &headlessFileUpload{}}, + {Path: "protocols/headless/file-upload-negative.yaml", TestCase: &headlessFileUploadNegative{}}, + {Path: "protocols/headless/headless-header-status-test.yaml", TestCase: &headlessHeaderStatus{}}, } type headlessBasic struct{} diff --git a/v2/cmd/integration-test/http.go b/v2/cmd/integration-test/http.go index 0409c2b5..71a0097b 100644 --- a/v2/cmd/integration-test/http.go +++ b/v2/cmd/integration-test/http.go @@ -29,57 +29,57 @@ import ( var httpTestcases = []TestCaseInfo{ // TODO: excluded due to parsing errors with console // "http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{}, - {Path: "http/get-headers.yaml", TestCase: &httpGetHeaders{}}, - {Path: "http/get-query-string.yaml", TestCase: &httpGetQueryString{}}, - {Path: "http/get-redirects.yaml", TestCase: &httpGetRedirects{}}, - {Path: "http/get-host-redirects.yaml", TestCase: &httpGetHostRedirects{}}, - {Path: "http/disable-redirects.yaml", TestCase: &httpDisableRedirects{}}, - {Path: "http/get.yaml", TestCase: &httpGet{}}, - {Path: "http/post-body.yaml", TestCase: &httpPostBody{}}, - {Path: "http/post-json-body.yaml", TestCase: &httpPostJSONBody{}}, - {Path: "http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}}, - {Path: "http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}}, - {Path: "http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}}, - {Path: "http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}}, - {Path: "http/raw-get.yaml", TestCase: &httpRawGet{}}, - {Path: "http/raw-with-params.yaml", TestCase: &httpRawWithParams{}}, - {Path: "http/raw-unsafe-with-params.yaml", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above - {Path: "http/raw-path-trailing-slash.yaml", TestCase: &httpRawPathTrailingSlash{}}, - {Path: "http/raw-payload.yaml", TestCase: &httpRawPayload{}}, - {Path: "http/raw-post-body.yaml", TestCase: &httpRawPostBody{}}, - {Path: "http/raw-unsafe-path.yaml", TestCase: &httpRawUnsafePath{}}, - {Path: "http/http-paths.yaml", TestCase: &httpPaths{}}, - {Path: "http/request-condition.yaml", TestCase: &httpRequestCondition{}}, - {Path: "http/request-condition-new.yaml", TestCase: &httpRequestCondition{}}, - {Path: "http/self-contained.yaml", TestCase: &httpRequestSelfContained{}}, - {Path: "http/self-contained-with-path.yaml", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above - {Path: "http/self-contained-with-params.yaml", TestCase: &httpRequestSelfContainedWithParams{}}, - {Path: "http/self-contained-file-input.yaml", TestCase: &httpRequestSelfContainedFileInput{}}, - {Path: "http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitive{}}, - {Path: "http/get.yaml,http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitiveCluster{}}, - {Path: "http/get-redirects-chain-headers.yaml", TestCase: &httpGetRedirectsChainHeaders{}}, - {Path: "http/dsl-matcher-variable.yaml", TestCase: &httpDSLVariable{}}, - {Path: "http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}}, - {Path: "http/race-simple.yaml", TestCase: &httpRaceSimple{}}, - {Path: "http/race-multiple.yaml", TestCase: &httpRaceMultiple{}}, - {Path: "http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}}, - {Path: "http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}}, - {Path: "http/variables.yaml", TestCase: &httpVariables{}}, - {Path: "http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}}, - {Path: "http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}}, - {Path: "http/get-sni.yaml", TestCase: &customCLISNI{}}, - {Path: "http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}}, - {Path: "http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}}, - {Path: "http/annotation-timeout.yaml", TestCase: &annotationTimeout{}}, - {Path: "http/custom-attack-type.yaml", TestCase: &customAttackType{}}, - {Path: "http/get-all-ips.yaml", TestCase: &scanAllIPS{}}, - {Path: "http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}}, - {Path: "http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}}, - {Path: "http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}}, - {Path: "http/save-extractor-values-to-file.yaml", TestCase: &httpSaveExtractorValuesToFile{}}, - {Path: "http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}}, - {Path: "http/matcher-status.yaml", TestCase: &matcherStatusTest{}}, - {Path: "http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}}, + {Path: "protocols/http/get-headers.yaml", TestCase: &httpGetHeaders{}}, + {Path: "protocols/http/get-query-string.yaml", TestCase: &httpGetQueryString{}}, + {Path: "protocols/http/get-redirects.yaml", TestCase: &httpGetRedirects{}}, + {Path: "protocols/http/get-host-redirects.yaml", TestCase: &httpGetHostRedirects{}}, + {Path: "protocols/http/disable-redirects.yaml", TestCase: &httpDisableRedirects{}}, + {Path: "protocols/http/get.yaml", TestCase: &httpGet{}}, + {Path: "protocols/http/post-body.yaml", TestCase: &httpPostBody{}}, + {Path: "protocols/http/post-json-body.yaml", TestCase: &httpPostJSONBody{}}, + {Path: "protocols/http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}}, + {Path: "protocols/http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}}, + {Path: "protocols/http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}}, + {Path: "protocols/http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}}, + {Path: "protocols/http/raw-get.yaml", TestCase: &httpRawGet{}}, + {Path: "protocols/http/raw-with-params.yaml", TestCase: &httpRawWithParams{}}, + {Path: "protocols/http/raw-unsafe-with-params.yaml", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above + {Path: "protocols/http/raw-path-trailing-slash.yaml", TestCase: &httpRawPathTrailingSlash{}}, + {Path: "protocols/http/raw-payload.yaml", TestCase: &httpRawPayload{}}, + {Path: "protocols/http/raw-post-body.yaml", TestCase: &httpRawPostBody{}}, + {Path: "protocols/http/raw-unsafe-path.yaml", TestCase: &httpRawUnsafePath{}}, + {Path: "protocols/http/http-paths.yaml", TestCase: &httpPaths{}}, + {Path: "protocols/http/request-condition.yaml", TestCase: &httpRequestCondition{}}, + {Path: "protocols/http/request-condition-new.yaml", TestCase: &httpRequestCondition{}}, + {Path: "protocols/http/self-contained.yaml", TestCase: &httpRequestSelfContained{}}, + {Path: "protocols/http/self-contained-with-path.yaml", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above + {Path: "protocols/http/self-contained-with-params.yaml", TestCase: &httpRequestSelfContainedWithParams{}}, + {Path: "protocols/http/self-contained-file-input.yaml", TestCase: &httpRequestSelfContainedFileInput{}}, + {Path: "protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitive{}}, + {Path: "protocols/http/get.yaml,protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitiveCluster{}}, + {Path: "protocols/http/get-redirects-chain-headers.yaml", TestCase: &httpGetRedirectsChainHeaders{}}, + {Path: "protocols/http/dsl-matcher-variable.yaml", TestCase: &httpDSLVariable{}}, + {Path: "protocols/http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}}, + {Path: "protocols/http/race-simple.yaml", TestCase: &httpRaceSimple{}}, + {Path: "protocols/http/race-multiple.yaml", TestCase: &httpRaceMultiple{}}, + {Path: "protocols/http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}}, + {Path: "protocols/http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}}, + {Path: "protocols/http/variables.yaml", TestCase: &httpVariables{}}, + {Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}}, + {Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}}, + {Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}}, + {Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}}, + {Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}}, + {Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}}, + {Path: "protocols/http/custom-attack-type.yaml", TestCase: &customAttackType{}}, + {Path: "protocols/http/get-all-ips.yaml", TestCase: &scanAllIPS{}}, + {Path: "protocols/http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}}, + {Path: "protocols/http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}}, + {Path: "protocols/http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}}, + {Path: "protocols/http/save-extractor-values-to-file.yaml", TestCase: &httpSaveExtractorValuesToFile{}}, + {Path: "protocols/http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}}, + {Path: "protocols/http/matcher-status.yaml", TestCase: &matcherStatusTest{}}, + {Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}}, } type httpInteractshRequest struct{} @@ -834,7 +834,7 @@ func (h *httpRawCookieReuse) Execute(filePath string) error { // // ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { // defer conn.Close() -// _, _ = conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test")) +// _, _ = conn.Write([]byte("protocols/http/1.1 200 OK\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test")) // }) // defer ts.Close() // diff --git a/v2/cmd/integration-test/integration-test.go b/v2/cmd/integration-test/integration-test.go index f878a76e..61f82daf 100644 --- a/v2/cmd/integration-test/integration-test.go +++ b/v2/cmd/integration-test/integration-test.go @@ -23,6 +23,7 @@ var ( debug = os.Getenv("DEBUG") == "true" githubAction = os.Getenv("GH_ACTION") == "true" customTests = os.Getenv("TESTS") + protocol = os.Getenv("PROTO") success = aurora.Green("[✓]").String() failed = aurora.Red("[✘]").String() @@ -38,15 +39,19 @@ var ( "headless": headlessTestcases, "whois": whoisTestCases, "ssl": sslTestcases, - "code": codeTestcases, + "library": libraryTestcases, "templatesPath": templatesPathTestCases, "templatesDir": templatesDirTestCases, "file": fileTestcases, "offlineHttp": offlineHttpTestcases, "customConfigDir": customConfigDirTestCases, "fuzzing": fuzzingTestCases, + "code": codeTestCases, + "multi": multiProtoTestcases, "generic": genericTestcases, "dsl": dslTestcases, + "flow": flowTestcases, + "javascript": jsTestcases, } // For debug purposes @@ -127,6 +132,11 @@ func runTests(customTemplatePaths []string) []string { var failedTestTemplatePaths []string for proto, testCaseInfos := range protocolTests { + if protocol != "" { + if !strings.EqualFold(proto, protocol) { + continue + } + } if len(customTemplatePaths) == 0 { fmt.Printf("Running test cases for %q protocol\n", aurora.Blue(proto)) } @@ -138,7 +148,7 @@ func runTests(customTemplatePaths []string) []string { if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) { var failedTemplatePath string var err error - if proto == "interactsh" { + if proto == "interactsh" || strings.Contains(testCaseInfo.Path, "interactsh") { failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount) } else { failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path) @@ -166,7 +176,7 @@ func execute(testCase testutils.TestCase, templatePath string) (string, error) { func expectResultsCount(results []string, expectedNumbers ...int) error { match := sliceutil.Contains(expectedNumbers, len(results)) if !match { - return fmt.Errorf("incorrect number of results: %d (actual) vs %v (expected) \nResults:\n\t%s\n", len(results), expectedNumbers, strings.Join(results, "\n\t")) + return fmt.Errorf("incorrect number of results: %d (actual) vs %v (expected) \nResults:\n\t%s\n", len(results), expectedNumbers, strings.Join(results, "\n\t")) // nolint:all } return nil } diff --git a/v2/cmd/integration-test/interactsh.go b/v2/cmd/integration-test/interactsh.go index 709954f6..38c0aabc 100644 --- a/v2/cmd/integration-test/interactsh.go +++ b/v2/cmd/integration-test/interactsh.go @@ -4,8 +4,8 @@ import osutils "github.com/projectdiscovery/utils/os" // All Interactsh related testcases var interactshTestCases = []TestCaseInfo{ - {Path: "http/interactsh.yaml", TestCase: &httpInteractshRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, - {Path: "http/interactsh-stop-at-first-match.yaml", TestCase: &httpInteractshStopAtFirstMatchRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, - {Path: "http/default-matcher-condition.yaml", TestCase: &httpDefaultMatcherCondition{}, DisableOn: func() bool { return true }}, // disable this test for now - {Path: "http/interactsh-requests-mc-and.yaml", TestCase: &httpInteractshRequestsWithMCAnd{}}, + {Path: "protocols/http/interactsh.yaml", TestCase: &httpInteractshRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, + {Path: "protocols/http/interactsh-stop-at-first-match.yaml", TestCase: &httpInteractshStopAtFirstMatchRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, + {Path: "protocols/http/default-matcher-condition.yaml", TestCase: &httpDefaultMatcherCondition{}, DisableOn: func() bool { return true }}, // disable this test for now + {Path: "protocols/http/interactsh-requests-mc-and.yaml", TestCase: &httpInteractshRequestsWithMCAnd{}}, } diff --git a/v2/cmd/integration-test/javascript.go b/v2/cmd/integration-test/javascript.go new file mode 100644 index 00000000..d0caafc6 --- /dev/null +++ b/v2/cmd/integration-test/javascript.go @@ -0,0 +1,155 @@ +package main + +import ( + "log" + "time" + + "github.com/ory/dockertest/v3" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" + osutils "github.com/projectdiscovery/utils/os" + "go.uber.org/multierr" +) + +var jsTestcases = []TestCaseInfo{ + {Path: "protocols/javascript/redis-pass-brute.yaml", TestCase: &javascriptRedisPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, + {Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, + {Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}}, +} + +var ( + redisResource *dockertest.Resource + sshResource *dockertest.Resource + pool *dockertest.Pool + defaultRetry = 3 +) + +type javascriptRedisPassBrute struct{} + +func (j *javascriptRedisPassBrute) Execute(filePath string) error { + if redisResource == nil || pool == nil { + // skip test as redis is not running + return nil + } + tempPort := redisResource.GetPort("6379/tcp") + finalURL := "localhost:" + tempPort + defer purge(redisResource) + errs := []error{} + for i := 0; i < defaultRetry; i++ { + results := []string{} + var err error + _ = pool.Retry(func() error { + //let ssh server start + time.Sleep(3 * time.Second) + results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug) + return nil + }) + if err != nil { + return err + } + if err := expectResultsCount(results, 1); err == nil { + return nil + } else { + errs = append(errs, err) + } + } + return multierr.Combine(errs...) +} + +type javascriptSSHServerFingerprint struct{} + +func (j *javascriptSSHServerFingerprint) Execute(filePath string) error { + if sshResource == nil || pool == nil { + // skip test as redis is not running + return nil + } + tempPort := sshResource.GetPort("2222/tcp") + finalURL := "localhost:" + tempPort + defer purge(sshResource) + errs := []error{} + for i := 0; i < defaultRetry; i++ { + results := []string{} + var err error + _ = pool.Retry(func() error { + //let ssh server start + time.Sleep(3 * time.Second) + results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug) + return nil + }) + if err != nil { + return err + } + if err := expectResultsCount(results, 1); err == nil { + return nil + } else { + errs = append(errs, err) + } + } + return multierr.Combine(errs...) +} + +// purge any given resource if it is not nil +func purge(resource *dockertest.Resource) { + if resource != nil && pool != nil { + containerName := resource.Container.Name + _ = pool.Client.StopContainer(resource.Container.ID, 0) + err := pool.Purge(resource) + if err != nil { + log.Printf("Could not purge resource: %s", err) + } + _ = pool.RemoveContainerByName(containerName) + } +} + +func init() { + // uses a sensible default on windows (tcp/http) and linux/osx (socket) + pool, err := dockertest.NewPool("") + if err != nil { + log.Printf("something went wrong with dockertest: %s", err) + return + } + + // uses pool to try to connect to Docker + err = pool.Client.Ping() + if err != nil { + log.Printf("Could not connect to Docker: %s", err) + } + + // setup a temporary redis instance + redisResource, err = pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "redis", + Tag: "latest", + Cmd: []string{"redis-server", "--requirepass", "iamadmin"}, + Platform: "linux/amd64", + }) + if err != nil { + log.Printf("Could not start resource: %s", err) + return + } + // by default expire after 30 sec + if err := redisResource.Expire(30); err != nil { + log.Printf("Could not expire resource: %s", err) + } + + // setup a temporary ssh server + sshResource, err = pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "lscr.io/linuxserver/openssh-server", + Tag: "latest", + Env: []string{ + "PUID=1000", + "PGID=1000", + "TZ=Etc/UTC", + "PASSWORD_ACCESS=true", + "USER_NAME=admin", + "USER_PASSWORD=admin", + }, + Platform: "linux/amd64", + }) + if err != nil { + log.Printf("Could not start resource: %s", err) + return + } + // by default expire after 30 sec + if err := sshResource.Expire(30); err != nil { + log.Printf("Could not expire resource: %s", err) + } +} diff --git a/v2/examples/simple.go b/v2/cmd/integration-test/library.go similarity index 58% rename from v2/examples/simple.go rename to v2/cmd/integration-test/library.go index e1f9a4cd..0dbce872 100644 --- a/v2/examples/simple.go +++ b/v2/cmd/integration-test/library.go @@ -4,14 +4,17 @@ import ( "context" "fmt" "log" + "net/http" + "net/http/httptest" "os" - "path/filepath" + "path" + "strings" "time" + "github.com/julienschmidt/httprouter" "github.com/logrusorgru/aurora" - + "github.com/pkg/errors" "github.com/projectdiscovery/goflags" - "github.com/projectdiscovery/httpx/common/httpx" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader" @@ -31,42 +34,78 @@ import ( "github.com/projectdiscovery/ratelimit" ) -func main() { +var libraryTestcases = []TestCaseInfo{ + {Path: "library/test.yaml", TestCase: &goIntegrationTest{}}, + {Path: "library/test.json", TestCase: &goIntegrationTest{}}, +} + +type goIntegrationTest struct{} + +// Execute executes a test case and returns an error if occurred +// +// Execute the docs at ../DESIGN.md if the code stops working for integration. +func (h *goIntegrationTest) Execute(templatePath string) error { + router := httprouter.New() + + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprintf(w, "This is test matcher text") + if strings.EqualFold(r.Header.Get("test"), "nuclei") { + fmt.Fprintf(w, "This is test headers matcher text") + } + }) + ts := httptest.NewServer(router) + defer ts.Close() + + results, err := executeNucleiAsLibrary(templatePath, ts.URL) + if err != nil { + return err + } + return expectResultsCount(results, 1) +} + +// executeNucleiAsLibrary contains an example +func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) { cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil) defer cache.Close() mockProgress := &testutils.MockProgressClient{} - reportingClient, _ := reporting.New(&reporting.Options{}, "") + reportingClient, err := reporting.New(&reporting.Options{}, "") + if err != nil { + return nil, err + } defer reportingClient.Close() outputWriter := testutils.NewMockOutputWriter() + var results []string outputWriter.WriteCallback = func(event *output.ResultEvent) { - fmt.Printf("Got Result: %v\n", event) + results = append(results, fmt.Sprintf("%v\n", event)) } defaultOpts := types.DefaultOptions() - protocolstate.Init(defaultOpts) - protocolinit.Init(defaultOpts) + _ = protocolstate.Init(defaultOpts) + _ = protocolinit.Init(defaultOpts) - defaultOpts.IncludeIds = goflags.StringSlice{"cname-service", "tech-detect"} + defaultOpts.Templates = goflags.StringSlice{templatePath} defaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags interactOpts := interactsh.DefaultOptions(outputWriter, reportingClient, mockProgress) interactClient, err := interactsh.New(interactOpts) if err != nil { - log.Fatalf("Could not create interact client: %s\n", err) + return nil, errors.Wrap(err, "could not create interact client") } defer interactClient.Close() home, _ := os.UserHomeDir() - catalog := disk.NewCatalog(filepath.Join(home, "nuclei-templates")) + catalog := disk.NewCatalog(path.Join(home, "nuclei-templates")) + ratelimiter := ratelimit.New(context.Background(), 150, time.Second) + defer ratelimiter.Stop() executerOpts := protocols.ExecutorOptions{ Output: outputWriter, Options: defaultOpts, Progress: mockProgress, Catalog: catalog, IssuesClient: reportingClient, - RateLimiter: ratelimit.New(context.Background(), 150, time.Second), + RateLimiter: ratelimiter, Interactsh: interactClient, HostErrorsCache: cache, Colorizer: aurora.NewAurora(true), @@ -83,24 +122,13 @@ func main() { store, err := loader.New(loader.NewConfig(defaultOpts, catalog, executerOpts)) if err != nil { - log.Fatalf("Could not create loader client: %s\n", err) + return nil, errors.Wrap(err, "could not create loader") } store.Load() - // flat input without probe - inputArgs := []*contextargs.MetaInput{{Input: "docs.hackerone.com"}} - input := &inputs.SimpleInputProvider{Inputs: inputArgs} - - httpxOptions := httpx.DefaultOptions - httpxOptions.Timeout = 5 * time.Second - httpxClient, err := httpx.New(&httpxOptions) - if err != nil { - log.Fatal(err) - } - - // use httpx to probe the URL => https://scanme.sh - input.SetWithProbe("scanme.sh", httpxClient) - + input := &inputs.SimpleInputProvider{Inputs: []*contextargs.MetaInput{{Input: templateURL}}} _ = engine.Execute(store.Templates(), input) engine.WorkPool().Wait() // Wait for the scan to finish + + return results, nil } diff --git a/v2/cmd/integration-test/loader.go b/v2/cmd/integration-test/loader.go index 92b682ce..2c3bdc1f 100644 --- a/v2/cmd/integration-test/loader.go +++ b/v2/cmd/integration-test/loader.go @@ -55,7 +55,7 @@ func (h *remoteTemplateList) Execute(templateList string) error { } defer os.Remove("test-config.yaml") - results, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-template-url", ts.URL+"/template_list", "-config", "test-config.yaml") + results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/template_list", "-config", "test-config.yaml") if err != nil { return err } @@ -78,7 +78,7 @@ func (h *excludedTemplate) Execute(templateList string) error { ts := httptest.NewServer(router) defer ts.Close() - results, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-t", templateList, "-include-templates", templateList) + results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-t", templateList, "-include-templates", templateList) if err != nil { return err } @@ -112,7 +112,7 @@ func (h *remoteTemplateListNotAllowed) Execute(templateList string) error { ts := httptest.NewServer(router) defer ts.Close() - _, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-template-url", ts.URL+"/template_list") + _, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/template_list") if err == nil { return fmt.Errorf("expected error for not allowed remote template list url") } @@ -154,7 +154,7 @@ func (h *remoteWorkflowList) Execute(workflowList string) error { } defer os.Remove("test-config.yaml") - results, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-workflow-url", ts.URL+"/workflow_list", "-config", "test-config.yaml") + results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/workflow_list", "-config", "test-config.yaml") if err != nil { return err } @@ -170,7 +170,7 @@ func (h *nonExistentTemplateList) Execute(nonExistingTemplateList string) error ts := httptest.NewServer(router) defer ts.Close() - _, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-template-url", ts.URL+"/404") + _, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/404") if err == nil { return fmt.Errorf("expected error for nonexisting workflow url") } @@ -186,7 +186,7 @@ func (h *nonExistentWorkflowList) Execute(nonExistingWorkflowList string) error ts := httptest.NewServer(router) defer ts.Close() - _, err := testutils.RunNucleiBareArgsAndGetResults(debug, "-target", ts.URL, "-workflow-url", ts.URL+"/404") + _, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/404") if err == nil { return fmt.Errorf("expected error for nonexisting workflow url") } diff --git a/v2/cmd/integration-test/multi.go b/v2/cmd/integration-test/multi.go new file mode 100644 index 00000000..a9ff58ff --- /dev/null +++ b/v2/cmd/integration-test/multi.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" +) + +var multiProtoTestcases = []TestCaseInfo{ + {Path: "protocols/multi/dynamic-values.yaml", TestCase: &multiProtoDynamicExtractor{}}, + {Path: "protocols/multi/evaluate-variables.yaml", TestCase: &multiProtoDynamicExtractor{}}, + {Path: "protocols/multi/exported-response-vars.yaml", TestCase: &multiProtoDynamicExtractor{}}, +} + +type multiProtoDynamicExtractor struct{} + +// Execute executes a test case and returns an error if occurred +func (h *multiProtoDynamicExtractor) Execute(templatePath string) error { + results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "blog.projectdiscovery.io", debug) + if err != nil { + return err + } + return expectResultsCount(results, 1) +} diff --git a/v2/cmd/integration-test/network.go b/v2/cmd/integration-test/network.go index f0c6a0e6..73aef67c 100644 --- a/v2/cmd/integration-test/network.go +++ b/v2/cmd/integration-test/network.go @@ -9,13 +9,13 @@ import ( ) var networkTestcases = []TestCaseInfo{ - {Path: "network/basic.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }}, - {Path: "network/hex.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }}, - {Path: "network/multi-step.yaml", TestCase: &networkMultiStep{}}, - {Path: "network/self-contained.yaml", TestCase: &networkRequestSelContained{}}, - {Path: "network/variables.yaml", TestCase: &networkVariables{}}, - {Path: "network/same-address.yaml", TestCase: &networkBasic{}}, - {Path: "network/network-port.yaml", TestCase: &networkPort{}}, + {Path: "protocols/network/basic.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }}, + {Path: "protocols/network/hex.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }}, + {Path: "protocols/network/multi-step.yaml", TestCase: &networkMultiStep{}}, + {Path: "protocols/network/self-contained.yaml", TestCase: &networkRequestSelContained{}}, + {Path: "protocols/network/variables.yaml", TestCase: &networkVariables{}}, + {Path: "protocols/network/same-address.yaml", TestCase: &networkBasic{}}, + {Path: "protocols/network/network-port.yaml", TestCase: &networkPort{}}, } const defaultStaticPort = 5431 diff --git a/v2/cmd/integration-test/offline-http.go b/v2/cmd/integration-test/offline-http.go index 8e442112..0a9500ca 100644 --- a/v2/cmd/integration-test/offline-http.go +++ b/v2/cmd/integration-test/offline-http.go @@ -7,16 +7,16 @@ import ( ) var offlineHttpTestcases = []TestCaseInfo{ - {Path: "offlinehttp/rfc-req-resp.yaml", TestCase: &RfcRequestResponse{}}, - {Path: "offlinehttp/offline-allowed-paths.yaml", TestCase: &RequestResponseWithAllowedPaths{}}, - {Path: "offlinehttp/offline-raw.yaml", TestCase: &RawRequestResponse{}}, + {Path: "protocols/offlinehttp/rfc-req-resp.yaml", TestCase: &RfcRequestResponse{}}, + {Path: "protocols/offlinehttp/offline-allowed-paths.yaml", TestCase: &RequestResponseWithAllowedPaths{}}, + {Path: "protocols/offlinehttp/offline-raw.yaml", TestCase: &RawRequestResponse{}}, } type RfcRequestResponse struct{} // Execute executes a test case and returns an error if occurred func (h *RfcRequestResponse) Execute(filePath string) error { - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "offlinehttp/data/", debug, "-passive") + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive") if err != nil { return err } @@ -28,7 +28,7 @@ type RequestResponseWithAllowedPaths struct{} // Execute executes a test case and returns an error if occurred func (h *RequestResponseWithAllowedPaths) Execute(filePath string) error { - results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "offlinehttp/data/", debug, "-passive") + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive") if err != nil { return err } @@ -40,7 +40,7 @@ type RawRequestResponse struct{} // Execute executes a test case and returns an error if occurred func (h *RawRequestResponse) Execute(filePath string) error { - _, err := testutils.RunNucleiTemplateAndGetResults(filePath, "offlinehttp/data/", debug, "-passive") + _, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive") if err == nil { return fmt.Errorf("incorrect result: no error (actual) vs error expected") } diff --git a/v2/cmd/integration-test/ssl.go b/v2/cmd/integration-test/ssl.go index 03b2e6bc..ecb79066 100644 --- a/v2/cmd/integration-test/ssl.go +++ b/v2/cmd/integration-test/ssl.go @@ -8,11 +8,11 @@ import ( ) var sslTestcases = []TestCaseInfo{ - {Path: "ssl/basic.yaml", TestCase: &sslBasic{}}, - {Path: "ssl/basic-ztls.yaml", TestCase: &sslBasicZtls{}}, - {Path: "ssl/custom-cipher.yaml", TestCase: &sslCustomCipher{}}, - {Path: "ssl/custom-version.yaml", TestCase: &sslCustomVersion{}}, - {Path: "ssl/ssl-with-vars.yaml", TestCase: &sslWithVars{}}, + {Path: "protocols/ssl/basic.yaml", TestCase: &sslBasic{}}, + {Path: "protocols/ssl/basic-ztls.yaml", TestCase: &sslBasicZtls{}}, + {Path: "protocols/ssl/custom-cipher.yaml", TestCase: &sslCustomCipher{}}, + {Path: "protocols/ssl/custom-version.yaml", TestCase: &sslCustomVersion{}}, + {Path: "protocols/ssl/ssl-with-vars.yaml", TestCase: &sslWithVars{}}, } type sslBasic struct{} diff --git a/v2/cmd/integration-test/template-dir.go b/v2/cmd/integration-test/template-dir.go index fe629fff..fb2f74c0 100644 --- a/v2/cmd/integration-test/template-dir.go +++ b/v2/cmd/integration-test/template-dir.go @@ -8,7 +8,7 @@ import ( ) var templatesDirTestCases = []TestCaseInfo{ - {Path: "dns/cname-fingerprint.yaml", TestCase: &templateDirWithTargetTest{}}, + {Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templateDirWithTargetTest{}}, } type templateDirWithTargetTest struct{} diff --git a/v2/cmd/integration-test/template-path.go b/v2/cmd/integration-test/template-path.go index 13c79d50..c5943aef 100644 --- a/v2/cmd/integration-test/template-path.go +++ b/v2/cmd/integration-test/template-path.go @@ -14,9 +14,9 @@ func getTemplatePath() string { var templatesPathTestCases = []TestCaseInfo{ //template folder path issue - {Path: "http/get.yaml", TestCase: &folderPathTemplateTest{}}, + {Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}}, //cwd - {Path: "./dns/cname-fingerprint.yaml", TestCase: &cwdTemplateTest{}}, + {Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}}, //relative path {Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}}, //absolute path diff --git a/v2/cmd/integration-test/websocket.go b/v2/cmd/integration-test/websocket.go index 9d0873ac..fc6508e0 100644 --- a/v2/cmd/integration-test/websocket.go +++ b/v2/cmd/integration-test/websocket.go @@ -10,10 +10,10 @@ import ( ) var websocketTestCases = []TestCaseInfo{ - {Path: "websocket/basic.yaml", TestCase: &websocketBasic{}}, - {Path: "websocket/cswsh.yaml", TestCase: &websocketCswsh{}}, - {Path: "websocket/no-cswsh.yaml", TestCase: &websocketNoCswsh{}}, - {Path: "websocket/path.yaml", TestCase: &websocketWithPath{}}, + {Path: "protocols/websocket/basic.yaml", TestCase: &websocketBasic{}}, + {Path: "protocols/websocket/cswsh.yaml", TestCase: &websocketCswsh{}}, + {Path: "protocols/websocket/no-cswsh.yaml", TestCase: &websocketNoCswsh{}}, + {Path: "protocols/websocket/path.yaml", TestCase: &websocketWithPath{}}, } type websocketBasic struct{} diff --git a/v2/cmd/integration-test/whois.go b/v2/cmd/integration-test/whois.go index 5caa846b..8e3954e5 100644 --- a/v2/cmd/integration-test/whois.go +++ b/v2/cmd/integration-test/whois.go @@ -5,7 +5,7 @@ import ( ) var whoisTestCases = []TestCaseInfo{ - {Path: "whois/basic.yaml", TestCase: &whoisBasic{}}, + {Path: "protocols/whois/basic.yaml", TestCase: &whoisBasic{}}, } type whoisBasic struct{} diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index 8f6ab1a8..4f4e3f9f 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -3,8 +3,10 @@ package main import ( "bufio" "fmt" + "io/fs" "os" "os/signal" + "path/filepath" "runtime" "runtime/pprof" "strings" @@ -21,6 +23,9 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/uncover" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/extensions" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/types" "github.com/projectdiscovery/nuclei/v2/pkg/types/scanstrategy" @@ -47,6 +52,42 @@ func main() { return } + // sign the templates if requested - only glob syntax is supported + if options.SignTemplates { + tsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys + if err != nil { + gologger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err) + } + + successCounter := 0 + errorCounter := 0 + for _, item := range options.Templates { + err := filepath.WalkDir(item, func(iterItem string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() || !strings.HasSuffix(iterItem, extensions.YAML) { + // skip non yaml files + return nil + } + + if err := templates.SignTemplate(tsigner, iterItem); err != nil { + if err != templates.ErrNotATemplate { + // skip warnings and errors as given items are not templates + errorCounter++ + gologger.Error().Msgf("could not sign '%s': %s\n", iterItem, err) + } + } else { + successCounter++ + } + + return nil + }) + if err != nil { + gologger.Error().Msgf("%s\n", err) + } + } + gologger.Info().Msgf("All templates signatures were elaborated success=%d failed=%d\n", successCounter, errorCounter) + return + } + // Profiling related code if memProfile != "" { f, err := os.Create(memProfile) @@ -145,6 +186,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVarP(&options.TemplateDisplay, "template-display", "td", false, "displays the templates content"), flagSet.BoolVar(&options.TemplateList, "tl", false, "list all available templates"), flagSet.StringSliceVarConfigOnly(&options.RemoteTemplateDomainList, "remote-template-domain", []string{"templates.nuclei.sh"}, "allowed domain list to load remote templates from"), + flagSet.BoolVar(&options.SignTemplates, "sign", false, "signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable"), ) flagSet.CreateGroup("filters", "Filtering", @@ -209,7 +251,6 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"), flagSet.StringVarP(&options.AttackType, "attack-type", "at", "", "type of payload combinations to perform (batteringram,pitchfork,clusterbomb)"), flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"), - flagSet.StringVar(&options.CustomConfigDir, "config-directory", "", "override the default config path ($home/.config)"), flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 10*1024*1024, "max response size to read in bytes"), flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", 1*1024*1024, "max response size to read in bytes"), flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"), @@ -309,7 +350,6 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVar(&options.EnableProgressBar, "stats", false, "display statistics about the running scan"), flagSet.BoolVarP(&options.StatsJSON, "stats-json", "sj", false, "display statistics in JSONL(ines) format"), 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.IntVarP(&options.MetricsPort, "metrics-port", "mp", 9092, "port to expose nuclei metrics on"), ) @@ -356,6 +396,11 @@ Run nuclei with sorted Markdown outputs (with environment variables): Additional documentation is available at: https://docs.nuclei.sh/getting-started/running `) + // nuclei has multiple migrations + // ex: resume.cfg moved to platform standard cache dir from config dir + // ex: config.yaml moved to platform standard config dir from linux specific config dir + // and hence it will be attempted in config package during init + goflags.DisableAutoConfigMigration = true _ = flagSet.Parse() gologger.DefaultLogger.SetTimestamp(options.Timestamp, levels.LevelDebug) @@ -368,8 +413,8 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started if options.LeaveDefaultPorts { http.LeaveDefaultPorts = true } - if options.CustomConfigDir != "" { - config.DefaultConfig.SetConfigDir(options.CustomConfigDir) + if customConfigDir := os.Getenv(config.NucleiConfigDirEnv); customConfigDir != "" { + config.DefaultConfig.SetConfigDir(customConfigDir) readFlagsConfig(flagSet) } if cfgFile != "" { @@ -391,7 +436,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started // cleanupOldResumeFiles cleans up resume files older than 10 days. func cleanupOldResumeFiles() { - root := config.DefaultConfig.GetConfigDir() + root := config.DefaultConfig.GetCacheDir() filter := fileutil.FileFilters{ OlderThan: 24 * time.Hour * 10, // cleanup on the 10th day Prefix: "resume-", @@ -436,6 +481,8 @@ func disableUpdatesCallback() { // printVersion prints the nuclei version and exits. func printVersion() { gologger.Info().Msgf("Nuclei Engine Version: %s", config.Version) + gologger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir()) + gologger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files os.Exit(0) } diff --git a/v2/cmd/sign-templates/main.go b/v2/cmd/sign-templates/main.go deleted file mode 100644 index 964d5ce3..00000000 --- a/v2/cmd/sign-templates/main.go +++ /dev/null @@ -1,149 +0,0 @@ -package main - -import ( - "io/fs" - "log" - "os" - "path/filepath" - - "github.com/pkg/errors" - "github.com/projectdiscovery/goflags" - "github.com/projectdiscovery/nuclei/v2/pkg/templates/extensions" - "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" - stringsutil "github.com/projectdiscovery/utils/strings" -) - -type options struct { - Templates goflags.StringSlice - Algorithm string - PrivateKeyName string - PrivateKeyPassPhrase string - PublicKeyName string -} - -func ParseOptions() (*options, error) { - opts := &options{} - flagSet := goflags.NewFlagSet() - flagSet.SetDescription(`sign-templates is a utility to perform template signature`) - - flagSet.CreateGroup("sign", "sign", - flagSet.StringSliceVarP(&opts.Templates, "templates", "t", nil, "templates files/folders to sign", goflags.CommaSeparatedStringSliceOptions), - flagSet.StringVarP(&opts.Algorithm, "algorithm", "a", "ecdsa", "signature algorithm (ecdsa, rsa)"), - flagSet.StringVarP(&opts.PrivateKeyName, "private-key", "prk", "", "private key env var name or file location"), - flagSet.StringVarP(&opts.PrivateKeyPassPhrase, "private-key-pass", "prkp", "", "private key passphrase env var name or file location"), - flagSet.StringVarP(&opts.PublicKeyName, "public-key", "puk", "", "public key env var name or file location"), - ) - - if err := flagSet.Parse(); err != nil { - return nil, err - } - - return opts, nil -} - -func main() { - opts, err := ParseOptions() - if err != nil { - log.Fatalf("couldn't parse options: %s\n", err) - } - - var algo signer.AlgorithmType - switch opts.Algorithm { - case "rsa": - algo = signer.RSA - case "ecdsa": - algo = signer.ECDSA - default: - log.Fatal("unknown algorithm type") - } - - signerOptions := &signer.Options{ - PrivateKeyName: opts.PrivateKeyName, - PassphraseName: opts.PrivateKeyPassPhrase, - PublicKeyName: opts.PublicKeyName, - Algorithm: algo, - } - sign, err := signer.New(signerOptions) - if err != nil { - log.Fatalf("couldn't create crypto engine: %s\n", err) - } - - for _, templateItem := range opts.Templates { - if err := processItem(sign, templateItem); err != nil { - log.Fatalf("Could not walk directory: %s\n", err) - } - } -} - -func processItem(sign *signer.Signer, item string) error { - return filepath.WalkDir(item, func(iterItem string, d fs.DirEntry, err error) error { - if err != nil || d.IsDir() { - return nil - } - - if err := processFile(sign, iterItem); err != nil { - return err - } - - return nil - }) -} - -func processFile(sign *signer.Signer, filePath string) error { - ext := filepath.Ext(filePath) - if !stringsutil.EqualFoldAny(ext, extensions.YAML) { - return nil - } - err := signTemplate(sign, filePath) - if err != nil { - return errors.Wrapf(err, "could not sign template: %s", filePath) - } - - ok, err := verifyTemplateSignature(sign, filePath) - if err != nil { - return errors.Wrapf(err, "could not verify template: %s", filePath) - } - if !ok { - return errors.Wrapf(err, "template signature doesn't match: %s", filePath) - } - - return nil -} - -func appendToFile(path string, data []byte, digest string) error { - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - if _, err := file.Write(data); err != nil { - return err - } - - if _, err := file.WriteString("\n" + digest); err != nil { - return err - } - return nil -} - -func signTemplate(sign *signer.Signer, templatePath string) error { - templateData, err := os.ReadFile(templatePath) - if err != nil { - return err - } - signatureData, err := signer.Sign(sign, templateData) - if err != nil { - return err - } - dataWithoutSignature := signer.RemoveSignatureFromData(templateData) - return appendToFile(templatePath, dataWithoutSignature, signatureData) -} - -func verifyTemplateSignature(sign *signer.Signer, templatePath string) (bool, error) { - templateData, err := os.ReadFile(templatePath) - if err != nil { - return false, err - } - return signer.Verify(sign, templateData) -} diff --git a/v2/detect-ssl-issuer.yaml b/v2/detect-ssl-issuer.yaml new file mode 100644 index 00000000..0b4c5027 --- /dev/null +++ b/v2/detect-ssl-issuer.yaml @@ -0,0 +1,20 @@ +id: ssl-issuer + +info: + name: Detect SSL Certificate Issuer + author: Lingtren + severity: info + description: | + Extract the issuer's organization from the target's certificate. Issuers are entities which sign and distribute certificates. + tags: ssl + metadata: + max-request: 1 + +ssl: + - address: "{{Host}}:{{Port}}" + + extractors: + - type: json + json: + - " .issuer_org[]" +# digest: 4b0a00483046022100bd4c4049c78917614a2b671e1221dcbe381ce1815e59b2417440e4a8eff70e13022100856bfb849ee53d189f9cdd14940de44c326f1a20837bf65bb5b4a3595fa33138 \ No newline at end of file diff --git a/v2/examples/advanced/advanced.go b/v2/examples/advanced/advanced.go new file mode 100644 index 00000000..86a871a2 --- /dev/null +++ b/v2/examples/advanced/advanced.go @@ -0,0 +1,47 @@ +package main + +import ( + nuclei "github.com/projectdiscovery/nuclei/v2/lib" + "github.com/remeh/sizedwaitgroup" +) + +func main() { + // create nuclei engine with options + ne, err := nuclei.NewThreadSafeNucleiEngine() + if err != nil { + panic(err) + } + // setup sizedWaitgroup to handle concurrency + sg := sizedwaitgroup.New(10) + + // scan 1 = run dns templates on scanme.sh + sg.Add() + go func() { + defer sg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, + nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), + ) + if err != nil { + panic(err) + } + }() + + // scan 2 = run templates with oast tags on honey.scanme.sh + sg.Add() + go func() { + defer sg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"http://honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}})) + if err != nil { + panic(err) + } + }() + + // wait for all scans to finish + sg.Wait() + defer ne.Close() + + // Output: + // [dns-saas-service-detection] scanme.sh + // [nameserver-fingerprint] scanme.sh + // [dns-saas-service-detection] honey.scanme.sh +} diff --git a/v2/examples/simple/simple.go b/v2/examples/simple/simple.go new file mode 100644 index 00000000..e9a881dd --- /dev/null +++ b/v2/examples/simple/simple.go @@ -0,0 +1,20 @@ +package main + +import nuclei "github.com/projectdiscovery/nuclei/v2/lib" + +func main() { + ne, err := nuclei.NewNucleiEngine( + nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}), + nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}), // optionally enable metrics server for better observability + ) + if err != nil { + panic(err) + } + // load targets and optionally probe non http/https targets + ne.LoadTargets([]string{"http://honey.scanme.sh"}, false) + err = ne.ExecuteWithCallback(nil) + if err != nil { + panic(err) + } + defer ne.Close() +} diff --git a/v2/go.mod b/v2/go.mod index b7f79f75..55e0b345 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -1,6 +1,6 @@ module github.com/projectdiscovery/nuclei/v2 -go 1.20 +go 1.21 require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible @@ -60,36 +60,57 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72 github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 github.com/charmbracelet/glamour v0.6.0 + github.com/denisenkom/go-mssqldb v0.12.3 + github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c github.com/docker/go-units v0.5.0 + github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e github.com/fatih/structs v1.1.0 github.com/go-git/go-git/v5 v5.7.0 + github.com/go-ldap/ldap/v3 v3.4.5 + github.com/go-pg/pg v8.0.7+incompatible + github.com/go-sql-driver/mysql v1.6.0 github.com/h2non/filetype v1.1.3 + github.com/hirochachacha/go-smb2 v1.1.0 github.com/klauspost/compress v1.16.7 github.com/labstack/echo/v4 v4.10.2 + github.com/lib/pq v1.10.1 github.com/mholt/archiver v3.1.1+incompatible + github.com/ory/dockertest/v3 v3.10.0 + github.com/praetorian-inc/fingerprintx v1.1.9 github.com/projectdiscovery/dsl v0.0.25 github.com/projectdiscovery/fasttemplate v0.0.2 - github.com/projectdiscovery/goflags v0.1.23 + github.com/projectdiscovery/goflags v0.1.24 github.com/projectdiscovery/gologger v1.1.11 + github.com/projectdiscovery/gostruct v0.0.1 + github.com/projectdiscovery/gozero v0.0.1 github.com/projectdiscovery/httpx v1.3.5 github.com/projectdiscovery/mapcidr v1.1.12 + github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 github.com/projectdiscovery/ratelimit v0.0.11 github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 github.com/projectdiscovery/sarif v0.0.1 github.com/projectdiscovery/tlsx v1.1.5 - github.com/projectdiscovery/uncover v1.0.6 + github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1 github.com/projectdiscovery/utils v0.0.58 github.com/projectdiscovery/wappalyzergo v0.0.109 + github.com/redis/go-redis/v9 v9.1.0 + github.com/ropnop/gokrb5/v8 v8.0.0-20201111231119-729746023c02 + github.com/sashabaranov/go-openai v1.15.3 github.com/stretchr/testify v1.8.4 + github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706 + golang.org/x/term v0.13.0 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 ) require ( aead.dev/minisign v0.2.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect @@ -100,38 +121,70 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bits-and-blooms/bitset v1.8.0 // indirect github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect + github.com/bytedance/sonic v1.8.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cheggaaa/pb/v3 v3.1.4 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cloudflare/cfssl v1.6.4 // indirect github.com/cloudflare/circl v1.3.3 // indirect + github.com/containerd/continuity v0.4.2 // indirect github.com/denisbrodbeck/machineid v1.0.1 // indirect - github.com/dlclark/regexp2 v1.8.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/docker/cli v24.0.5+incompatible // indirect + github.com/docker/docker v24.0.5+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect github.com/fatih/color v1.15.0 // indirect + github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gaukas/godicttls v0.0.4 // indirect + github.com/geoffgarside/ber v1.1.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/google/certificate-transparency-go v1.1.4 // indirect github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.0.0 // indirect + github.com/jcmturner/rpc/v2 v2.0.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/kataras/jwt v0.1.8 // indirect + github.com/kataras/jwt v0.1.10 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mackerelio/go-osstat v0.2.4 // indirect github.com/minio/selfupdate v0.6.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.1.9 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/projectdiscovery/asnmap v1.0.5 // indirect github.com/projectdiscovery/cdncheck v1.0.9 // indirect github.com/projectdiscovery/freeport v0.0.5 // indirect - github.com/projectdiscovery/gostruct v0.0.1 // indirect github.com/quic-go/quic-go v0.38.1 // indirect github.com/refraction-networking/utls v1.5.4 // indirect - github.com/sashabaranov/go-openai v1.14.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.1.1 // indirect github.com/smartystreets/assertions v1.0.0 // indirect github.com/tidwall/btree v1.6.0 // indirect @@ -142,12 +195,20 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect + github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/ysmood/fetchup v0.2.3 // indirect github.com/ysmood/got v0.34.1 // indirect github.com/yuin/goldmark v1.5.4 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect github.com/zeebo/blake3 v0.2.3 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect + mellium.im/sasl v0.3.1 // indirect ) require ( @@ -213,16 +274,16 @@ require ( github.com/ysmood/leakless v0.8.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect - github.com/zmap/zcrypto v0.0.0-20230814193918-dbe676986518 // indirect + github.com/zmap/zcrypto v0.0.0-20230829152017-3b5d61809233 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.uber.org/zap v1.25.0 // indirect goftp.io/server/v2 v2.0.1 // indirect - golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/mod v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect @@ -230,7 +291,7 @@ require ( ) require ( - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/alecthomas/chroma v0.10.0 @@ -243,11 +304,12 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect github.com/aws/smithy-go v1.13.5 // indirect + github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/imdario/mergo v0.3.15 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/labstack/gommon v0.4.0 // indirect diff --git a/v2/go.sum b/v2/go.sum index 81d0b6de..bbaaaca2 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -1,43 +1,93 @@ aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8= git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/gostackparse v0.6.0 h1:egCGQviIabPwsyoWpGvIBGrEnNWez35aEO7OJ1vBI4o= github.com/DataDog/gostackparse v0.6.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE= github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4= github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8= github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4= github.com/Mzack9999/ldapserver v1.0.2-0.20211229000134-b44a0d6ad0dd h1:RTWs+wEY9efxTKK5aFic5C5KybqQelGcX+JdM69KoTo= github.com/Mzack9999/ldapserver v1.0.2-0.20211229000134-b44a0d6ad0dd/go.mod h1:AqtPw7WNT0O69k+AbPKWVGYeW94TqgMW/g+Ppc8AZr4= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -46,11 +96,17 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 h1:NjwIgLQlD46o79bheVG4SCdRnnOz4XtgUN1WABX5DLA= github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= +github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= @@ -59,16 +115,26 @@ github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E= github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8= github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw= github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA= github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +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-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/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-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 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k= github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= @@ -113,76 +179,191 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ 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/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= +github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= +github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/caddyserver/certmagic v0.19.2 h1:HZd1AKLx4592MalEGQS39DKs2ZOAJCEM/xYPMQ2/ui0= github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE= github.com/corpix/uarand v0.2.0/go.mod h1:/3Z1QIqWkDIhf6XWn/08/uMHoQ8JUoTIKc2iPchBOmM= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= +github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= -github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= +github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= +github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e h1:UvQD6hTSfeM6hhTQ24Dlw2RppP05W7SWbWb6kubJAog= +github.com/dop251/goja v0.0.0-20230828202809-3dbe69dd2b8e/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 h1:ObbB2tzHWWAxzsG5futqeq2Ual2zYlo/+eMkSc5sn8w= +github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562/go.mod h1:X2TOTJ+Uamd454RFp7ig2tmP3hQg0Z2Qk8gbVQmU0mk= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b h1:XMw3j+4AEXLeL/uyiZ7/qYE1X7Ul05RTwWBhzxCLi+0= +github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vojDomW5jdDJhIS60KdbrE9uPYhyAq/7OnF4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= +github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= +github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g= +github.com/go-pg/pg v8.0.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -191,7 +372,14 @@ github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+j github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-rod/rod v0.114.0 h1:P+zLOqsj+vKf4C86SfjP6ymyPl9VXoYKm+ceCeQms6Y= github.com/go-rod/rod v0.114.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw= github.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -200,17 +388,52 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -218,10 +441,19 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY= github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -236,34 +468,89 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/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.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/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.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk= github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA= github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A= github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ= +github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= +github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= @@ -272,34 +559,65 @@ github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZn github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= +github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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.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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kataras/jwt v0.1.8 h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk= -github.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o= +github.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ= +github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -313,8 +631,12 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A= @@ -323,21 +645,28 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= @@ -345,6 +674,7 @@ github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1 github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= @@ -352,13 +682,27 @@ github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tB github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -366,33 +710,79 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= +github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/praetorian-inc/fingerprintx v1.1.9 h1:zWbG/Fdan0s/dvXkeaHb/CdFTz/yEEzrAF4iCzok3r8= +github.com/praetorian-inc/fingerprintx v1.1.9/go.mod h1:k6EJIHe/Da4DH5e4JuoZHe+qSGq/KPUmXGaK+xW74OI= github.com/projectdiscovery/asnmap v1.0.5 h1:euWZuyLUkWhRMi8x3b4JXvKBDU9qRIcrh6p9BtYe4LM= github.com/projectdiscovery/asnmap v1.0.5/go.mod h1:7YUiCMshTEKzPlV1kgVp7l6Z7ZrAvxH6ufYq5mNnurY= github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= @@ -409,12 +799,14 @@ github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOi github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw= github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q= github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE= -github.com/projectdiscovery/goflags v0.1.23 h1:/mbzTf0T76CyCBQ+ofDGjpBI+ExhpxTA49MJtgAoa2U= -github.com/projectdiscovery/goflags v0.1.23/go.mod h1:15Hc92q689vj6bJv0NdLb9NT/PTrPtT9ItSZBUAJULs= +github.com/projectdiscovery/goflags v0.1.24 h1:eYMUgohu/pJLhA6e8iUYJ9DIiXn61FRzKbywV4iea4s= +github.com/projectdiscovery/goflags v0.1.24/go.mod h1:qTJZXaPNaJ4jARphm2y09HpYUTYbWaW3gcoQnS6Lrmo= github.com/projectdiscovery/gologger v1.1.11 h1:8vsz9oJlDT9euw6xlj7F7dZ6RWItVIqVwn4Mr6uzky8= github.com/projectdiscovery/gologger v1.1.11/go.mod h1:UR2bgXl7zraOxYGnUwuO917hifWrwMJ0feKnVqMQkzY= github.com/projectdiscovery/gostruct v0.0.1 h1:1KvR6Pn4mDbQqoLEQzhRfHpbreLno2R9xqRCCt5tgmU= github.com/projectdiscovery/gostruct v0.0.1/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE= +github.com/projectdiscovery/gozero v0.0.1 h1:f08ZnYlbDZV/TNGDvIXV9s/oB/sAI+HWaSbW4em4aKM= +github.com/projectdiscovery/gozero v0.0.1/go.mod h1:/dHwbly+1lhOX9UreVure4lEe7K4hIHeu/c/wZGNTDo= github.com/projectdiscovery/hmap v0.0.22 h1:iBddf2s45zTF+fNIw2WdQVJT2nJKI6p8Ie2c/eEOw2I= github.com/projectdiscovery/hmap v0.0.22/go.mod h1:0oVrzemluM43mi8ZKCQMfZvlOPrk0Vn2eiiyyZDILk8= github.com/projectdiscovery/httpx v1.3.5 h1:opyRTMsi9ccNptOnTin4Dp2NTmLC9ewgVjG8+q+luhM= @@ -423,6 +815,8 @@ github.com/projectdiscovery/interactsh v1.1.7 h1:rK+eKklyM+4qaLS+1MgtHDvrHdAnSTM github.com/projectdiscovery/interactsh v1.1.7/go.mod h1:WYxbcV0fz3LMf83mugCYo5VUsBb4nfIdAVK6GVJhobs= github.com/projectdiscovery/mapcidr v1.1.12 h1:hVgTpEAeLhYixDPdcDXaL2MXK3q+3rFdxAoCgbPLxIc= github.com/projectdiscovery/mapcidr v1.1.12/go.mod h1:hIRTgZEnI7+moVgeipU6A5yv3o+VrUw0tE/3GJw3Y2Y= +github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8= +github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc= github.com/projectdiscovery/networkpolicy v0.0.6 h1:yDvm0XCrS9HeemRrBS+J+22surzVczM94W5nHiOy/1o= github.com/projectdiscovery/networkpolicy v0.0.6/go.mod h1:8HJQ/33Pi7v3a3MRWIQGXzpj+zHw2d60TysEL4qdoQk= github.com/projectdiscovery/ratelimit v0.0.11 h1:QPIIo8ACGd2YayOQ/EdQOP3hd+X+9afSbC34wa3eiyI= @@ -438,18 +832,54 @@ github.com/projectdiscovery/retryablehttp-go v1.0.31/go.mod h1:pFBFbxnb7fupJbl99 github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us= github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ= github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA= +github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0= github.com/projectdiscovery/tlsx v1.1.5 h1:S8KV2ckcjW3hDBa/REmDdsZfHwYJ9eKoZ7rtgETkwkM= github.com/projectdiscovery/tlsx v1.1.5/go.mod h1:0a0TdWb3fYeVpuPsJuf5AGtwZIKwkY0kxdO9lojU6S4= -github.com/projectdiscovery/uncover v1.0.6 h1:Q6glbMr8go7X39iW/7yojheaV+5r16llvY420nruihI= -github.com/projectdiscovery/uncover v1.0.6/go.mod h1:lWJ/QXZu55rHUFFSeFVYFMzkUeUcfP0hUEJm0EYFxnc= +github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1 h1:Pu6LvDqn+iSlhCDKKWm1ItPc++kqqlU8OntZeB/Prak= +github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1/go.mod h1:Drl/CWD392mKtdXJhCBPlMkM0I6671pqedFphcnK5f8= github.com/projectdiscovery/utils v0.0.58 h1:kk2AkSO84QZc9rDRI8jWA2Iia4uzb4sUcfh4h0xA20I= github.com/projectdiscovery/utils v0.0.58/go.mod h1:rsR5Kzjrb+/Yp7JSnEblLk4LfU4zH5Z7wQn8RzaGSdY= github.com/projectdiscovery/wappalyzergo v0.0.109 h1:BERfwTRn1dvB1tbhyc5m67R8VkC9zbVuPsEq4VEm07k= github.com/projectdiscovery/wappalyzergo v0.0.109/go.mod h1:4Z3DKhi75zIPMuA+qSDDWxZvnhL4qTLmDx4dxNMu7MA= github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE= github.com/projectdiscovery/yamldoc-go v1.0.4/go.mod h1:8PIPRcUD55UbtQdcfFR1hpIGRWG0P7alClXNGt1TBik= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= +github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= @@ -458,13 +888,23 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/ropnop/gokrb5/v8 v8.0.0-20201111231119-729746023c02 h1:Nk74A6E84pynxLN74hIrQ7Q3cS0/0L5I7coOLNSFAMs= +github.com/ropnop/gokrb5/v8 v8.0.0-20201111231119-729746023c02/go.mod h1:OGEfzIZJs5m/VgAb1BvWR8fH17RTQWx84HTB1koGf9s= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +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/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/sashabaranov/go-openai v1.14.2 h1:5DPTtR9JBjKPJS008/A409I5ntFhUPPGCmaAihcPRyo= -github.com/sashabaranov/go-openai v1.14.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sashabaranov/go-openai v1.15.3 h1:rzoNK9n+Cak+PM6OQ9puxDmFllxfnVea9StlmhglXqA= +github.com/sashabaranov/go-openai v1.15.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -476,23 +916,36 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= 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/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -505,12 +958,14 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA= @@ -521,6 +976,7 @@ github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vl github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -530,34 +986,55 @@ github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= +github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw= +github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA= github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222 h1:h2JizvZl9aIj6za9S5AyrkU+OzIS4CetQthH/ejO+lg= github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ= +github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE= github.com/xanzy/go-gitlab v0.84.0 h1:PdpCaskQSgcVDsx21c6ikf8Rfyo7SNtFAJwP9PrbCFE= github.com/xanzy/go-gitlab v0.84.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= @@ -574,6 +1051,9 @@ github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -596,26 +1076,55 @@ github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54t github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= -github.com/zmap/zcrypto v0.0.0-20230814193918-dbe676986518 h1:O8GHQBxrphDuNhJQdKBHwP3JQUtZUyi3b+jjPYmF7oA= -github.com/zmap/zcrypto v0.0.0-20230814193918-dbe676986518/go.mod h1:Z2SNNuFhO+AAsezbGEHTWeW30hHv5niUYT3fwJ61Nl0= +github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w= +github.com/zmap/zcrypto v0.0.0-20230829152017-3b5d61809233 h1:6J0/zRqCBtXnPOs9Iw2o9QoPfWmcq6KJJcpRPHoyPzM= +github.com/zmap/zcrypto v0.0.0-20230829152017-3b5d61809233/go.mod h1:Z2SNNuFhO+AAsezbGEHTWeW30hHv5niUYT3fwJ61Nl0= +github.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXDUD+uue8yeLHr0eXx1lvY6CvMiHbTKw5nGmA9OUoo= +github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706 h1:LaMyYFWQA7kh3ovPfAaFDTKlJu3JGng8khruOtsBVnE= +github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706/go.mod h1:re2kMcs84XHb8Xl6RInt0emoKCuphfmfjHYuteviLHQ= github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE= goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= @@ -626,27 +1135,89 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= @@ -658,27 +1229,73 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -686,12 +1303,17 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -717,12 +1339,16 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -730,27 +1356,158 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= @@ -760,12 +1517,17 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/corvus-ch/zbase32.v1 v1.0.0 h1:K4u1NprbDNvKPczKfHLbwdOWHTZ0zfv2ow71H1nRnFU= gopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DSo0nK9U+c0Y= gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= @@ -776,15 +1538,36 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +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= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/v2/internal/installer/versioncheck.go b/v2/internal/installer/versioncheck.go index 47a79105..fa920524 100644 --- a/v2/internal/installer/versioncheck.go +++ b/v2/internal/installer/versioncheck.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "runtime" + "sync" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/retryablehttp-go" @@ -34,7 +35,56 @@ type PdtmAPIResponse struct { // and returns an error if it fails to check on success it returns nil and changes are // made to the default config in config.DefaultConfig func NucleiVersionCheck() error { - resp, err := retryableHttpClient.Get(pdtmNucleiVersionEndpoint + "?" + getpdtmParams()) + return doVersionCheck(false) +} + +// this will be updated by features of 1.21 release (which directly provides sync.Once(func())) +type sdkUpdateCheck struct { + sync.Once +} + +var sdkUpdateCheckInstance = &sdkUpdateCheck{} + +// NucleiSDKVersionCheck checks for latest version of nuclei which running in sdk mode +// this only happens once per process regardless of how many times this function is called +func NucleiSDKVersionCheck() { + sdkUpdateCheckInstance.Do(func() { + _ = doVersionCheck(true) + }) +} + +// getpdtmParams returns encoded query parameters sent to update check endpoint +func getpdtmParams(isSDK bool) string { + params := &url.Values{} + params.Add("os", runtime.GOOS) + params.Add("arch", runtime.GOARCH) + params.Add("go_version", runtime.Version()) + params.Add("v", config.Version) + if isSDK { + params.Add("sdk", "true") + } + params.Add("utm_source", getUtmSource()) + return params.Encode() +} + +// UpdateIgnoreFile updates default ignore file by downloading latest ignore file +func UpdateIgnoreFile() error { + resp, err := retryableHttpClient.Get(pdtmNucleiIgnoreFileEndpoint + "?" + getpdtmParams(false)) + if err != nil { + return err + } + bin, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + if err := os.WriteFile(config.DefaultConfig.GetIgnoreFilePath(), bin, 0644); err != nil { + return err + } + return config.DefaultConfig.UpdateNucleiIgnoreHash() +} + +func doVersionCheck(isSDK bool) error { + resp, err := retryableHttpClient.Get(pdtmNucleiVersionEndpoint + "?" + getpdtmParams(isSDK)) if err != nil { return err } @@ -63,30 +113,3 @@ func NucleiVersionCheck() error { } return config.DefaultConfig.WriteVersionCheckData(pdtmResp.IgnoreHash, nucleiversion, templateversion) } - -// getpdtmParams returns encoded query parameters sent to update check endpoint -func getpdtmParams() string { - params := &url.Values{} - params.Add("os", runtime.GOOS) - params.Add("arch", runtime.GOARCH) - params.Add("go_version", runtime.Version()) - params.Add("v", config.Version) - params.Add("utm_source", getUtmSource()) - return params.Encode() -} - -// UpdateIgnoreFile updates default ignore file by downloading latest ignore file -func UpdateIgnoreFile() error { - resp, err := retryableHttpClient.Get(pdtmNucleiIgnoreFileEndpoint + "?" + getpdtmParams()) - if err != nil { - return err - } - bin, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - if err := os.WriteFile(config.DefaultConfig.GetIgnoreFilePath(), bin, 0644); err != nil { - return err - } - return config.DefaultConfig.UpdateNucleiIgnoreHash() -} diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go index f943dfef..fb81b0ba 100644 --- a/v2/internal/runner/options.go +++ b/v2/internal/runner/options.go @@ -20,6 +20,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" + protocoltypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/types" fileutil "github.com/projectdiscovery/utils/file" "github.com/projectdiscovery/utils/generic" @@ -49,6 +50,7 @@ func ParseOptions(options *types.Options) { // Read the inputs and configure the logging configureOutput(options) + // Show the user the banner showBanner() @@ -68,7 +70,7 @@ func ParseOptions(options *types.Options) { } // Validate the options passed by the user and if any // invalid options have been used, exit. - if err := validateOptions(options); err != nil { + if err := ValidateOptions(options); err != nil { gologger.Fatal().Msgf("Program exiting: %s\n", err) } @@ -98,7 +100,7 @@ func ParseOptions(options *types.Options) { } // validateOptions validates the configuration options passed -func validateOptions(options *types.Options) error { +func ValidateOptions(options *types.Options) error { validate := validator.New() if err := validate.Struct(options); err != nil { if _, ok := err.(*validator.InvalidValidationError); ok { @@ -354,6 +356,9 @@ func validateCertificatePaths(certificatePaths ...string) { func readEnvInputVars(options *types.Options) { if strings.EqualFold(os.Getenv("NUCLEI_CLOUD"), "true") { options.Cloud = true + + // TODO: disable files, offlinehttp, code + options.ExcludeProtocols = append(options.ExcludeProtocols, protocoltypes.CodeProtocol, protocoltypes.FileProtocol, protocoltypes.OfflineHTTPProtocol) } if options.CloudURL = os.Getenv("NUCLEI_CLOUD_SERVER"); options.CloudURL == "" { options.CloudURL = "https://cloud-dev.nuclei.sh" @@ -401,6 +406,10 @@ func readEnvInputVars(options *types.Options) { options.AzureClientSecret = os.Getenv("AZURE_CLIENT_SECRET") options.AzureServiceURL = os.Getenv("AZURE_SERVICE_URL") + // Custom public keys for template verification + options.CodeTemplateSignaturePublicKey = os.Getenv("NUCLEI_SIGNATURE_PUBLIC_KEY") + options.CodeTemplateSignatureAlgorithm = os.Getenv("NUCLEI_SIGNATURE_ALGORITHM") + // General options to disable the template download locations from being used. // This will override the default behavior of downloading templates from the default locations as well as the // custom locations. diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index 7497b3ec..45c4b699 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -261,7 +261,7 @@ func New(options *types.Options) (*Runner, error) { statsInterval = -1 options.EnableProgressBar = true } - runner.progress, progressErr = progress.NewStatsTicker(statsInterval, options.EnableProgressBar, options.StatsJSON, options.Metrics, options.Cloud, options.MetricsPort) + runner.progress, progressErr = progress.NewStatsTicker(statsInterval, options.EnableProgressBar, options.StatsJSON, options.Cloud, options.MetricsPort) if progressErr != nil { return nil, progressErr } @@ -641,16 +641,6 @@ func (r *Runner) executeSmartWorkflowInput(executorOpts protocols.ExecutorOption } func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { - var unclusteredRequests int64 - for _, template := range store.Templates() { - // workflows will dynamically adjust the totals while running, as - // it can't be known in advance which requests will be called - if len(template.Workflows) > 0 { - continue - } - unclusteredRequests += int64(template.TotalRequests) * r.hmapInputProvider.Count() - } - if r.options.VerboseVerbose { for _, template := range store.Templates() { r.logAvailableTemplate(template.Path) @@ -660,34 +650,15 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine) } } - // Cluster the templates first because we want info on how many - // templates did we cluster for showing to user in CLI - originalTemplatesCount := len(store.Templates()) - finalTemplates, clusterCount := templates.ClusterTemplates(store.Templates(), engine.ExecuterOptions()) + finalTemplates := []*templates.Template{} + finalTemplates = append(finalTemplates, store.Templates()...) finalTemplates = append(finalTemplates, store.Workflows()...) - var totalRequests int64 - for _, t := range finalTemplates { - if len(t.Workflows) > 0 { - continue - } - totalRequests += int64(t.Executer.Requests()) * r.hmapInputProvider.Count() - } - if totalRequests < unclusteredRequests { - gologger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, unclusteredRequests-totalRequests) - } - workflowCount := len(store.Workflows()) - templateCount := originalTemplatesCount + workflowCount - - // 0 matches means no templates were found in the directory - if templateCount == 0 { - return &atomic.Bool{}, errors.New("no valid templates were found") + if len(finalTemplates) == 0 { + return nil, errors.New("no templates provided for scan") } - // tracks global progress and captures stdout/stderr until p.Wait finishes - r.progress.Init(r.hmapInputProvider.Count(), templateCount, totalRequests) - - results := engine.ExecuteScanWithOpts(finalTemplates, r.hmapInputProvider, true) + results := engine.ExecuteScanWithOpts(finalTemplates, r.hmapInputProvider, r.options.DisableClustering) return results, nil } @@ -697,6 +668,12 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) { stats.Display(parsers.SyntaxWarningStats) stats.Display(parsers.SyntaxErrorStats) stats.Display(parsers.RuntimeWarningsStats) + if r.options.Verbose { + // only print these stats in verbose mode + stats.DisplayAsWarning(parsers.HeadlessFlagWarningStats) + stats.DisplayAsWarning(parsers.TemplatesExecutedStats) + } + stats.DisplayAsWarning(parsers.UnsignedWarning) cfg := config.DefaultConfig @@ -710,6 +687,15 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) { if len(store.Workflows()) > 0 { gologger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows())) } + for k, v := range templates.SignatureStats { + if v.Load() > 0 { + if k != templates.Unsigned { + gologger.Info().Msgf("Executing %d signed templates from %s", v.Load(), k) + } else if !r.options.Silent { + gologger.DefaultLogger.Print().Msgf("[%v] Executing %d unsigned templates. Use with caution.", aurora.BrightYellow("WRN"), v.Load()) + } + } + } if r.hmapInputProvider.Count() > 0 { gologger.Info().Msgf("Targets loaded for current scan: %d", r.hmapInputProvider.Count()) } diff --git a/v2/key.go b/v2/key.go new file mode 100644 index 00000000..cc5adad5 --- /dev/null +++ b/v2/key.go @@ -0,0 +1,6 @@ +package v2 + +import _ "embed" + +//go:embed nuclei.crt +var NucleiCert []byte diff --git a/v2/lib/README.md b/v2/lib/README.md new file mode 100644 index 00000000..fdfc22ce --- /dev/null +++ b/v2/lib/README.md @@ -0,0 +1,87 @@ +## Using Nuclei as Library + +Nuclei was primarily built as a CLI tool, but with increasing choice of users wanting to use nuclei as library in their own automation, we have added a simplified Library/SDK of nuclei in v3 + +### Installation + +To add nuclei as a library to your go project, you can use the following command: + +```bash +go get -u github.com/projectdiscovery/nuclei/v2/lib +``` + +Or add below import to your go file and let IDE handle the rest: + +```go +import nuclei "github.com/projectdiscovery/nuclei/v2/lib" +``` + +## Basic Example of using Nuclei Library/SDK + +```go +// create nuclei engine with options + ne, err := nuclei.NewNucleiEngine( + nuclei.WithTemplateFilters(nuclei.TemplateFilters{Severity: "critical"}), // run critical severity templates only + ) + if err != nil { + panic(err) + } + // load targets and optionally probe non http/https targets + ne.LoadTargets([]string{"scanme.sh"}, false) + err = ne.ExecuteWithCallback(nil) + if err != nil { + panic(err) + } + defer ne.Close() +``` + +## Advanced Example of using Nuclei Library/SDK + +For Various use cases like batching etc you might want to run nuclei in goroutines this can be done by using `nuclei.NewThreadSafeNucleiEngine` + +```go +// create nuclei engine with options + ne, err := nuclei.NewThreadSafeNucleiEngine() + if err != nil{ + panic(err) + } + // setup waitgroup to handle concurrency + wg := &sync.WaitGroup{} + + // scan 1 = run dns templates on scanme.sh + wg.Add(1) + go func() { + defer wg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "http"})) + if err != nil { + panic(err) + } + }() + + // scan 2 = run http templates on honey.scanme.sh + wg.Add(1) + go func() { + defer wg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) + if err != nil { + panic(err) + } + }() + + // wait for all scans to finish + wg.Wait() + defer ne.Close() +``` + +## More Documentation + +For complete documentation of nuclei library, please refer to [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v2/lib) which contains all available options and methods. + + + +### Note + +| :exclamation: **Disclaimer** | +|---------------------------------| +| **This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating. | +| This project was primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. | \ No newline at end of file diff --git a/v2/lib/config.go b/v2/lib/config.go new file mode 100644 index 00000000..e6977984 --- /dev/null +++ b/v2/lib/config.go @@ -0,0 +1,321 @@ +package nuclei + +import ( + "context" + "time" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" + "github.com/projectdiscovery/ratelimit" +) + +// TemplateSources contains template sources +// which define where to load templates from +type TemplateSources struct { + Templates []string // template file/directory paths + Workflows []string // workflow file/directory paths + RemoteTemplates []string // remote template urls + RemoteWorkflows []string // remote workflow urls + TrustedDomains []string // trusted domains for remote templates/workflows +} + +// WithTemplatesOrWorkflows sets templates / workflows to use /load +func WithTemplatesOrWorkflows(sources TemplateSources) NucleiSDKOptions { + return func(e *NucleiEngine) error { + // by default all of these values are empty + e.opts.Templates = sources.Templates + e.opts.Workflows = sources.Workflows + e.opts.TemplateURLs = sources.RemoteTemplates + e.opts.WorkflowURLs = sources.RemoteWorkflows + e.opts.RemoteTemplateDomainList = append(e.opts.RemoteTemplateDomainList, sources.TrustedDomains...) + return nil + } +} + +// config contains all SDK configuration options +type TemplateFilters struct { + Severity string // filter by severities (accepts CSV values of info, low, medium, high, critical) + ExcludeSeverities string // filter by excluding severities (accepts CSV values of info, low, medium, high, critical) + ProtocolTypes string // filter by protocol types + ExcludeProtocolTypes string // filter by excluding protocol types + Authors []string // fiter by author + Tags []string // filter by tags present in template + ExcludeTags []string // filter by excluding tags present in template + IncludeTags []string // filter by including tags present in template + IDs []string // filter by template IDs + ExcludeIDs []string // filter by excluding template IDs + TemplateCondition []string // DSL condition/ expression +} + +// WithTemplateFilters sets template filters and only templates matching the filters will be +// loaded and executed +func WithTemplateFilters(filters TemplateFilters) NucleiSDKOptions { + return func(e *NucleiEngine) error { + s := severity.Severities{} + if err := s.Set(filters.Severity); err != nil { + return err + } + es := severity.Severities{} + if err := es.Set(filters.ExcludeSeverities); err != nil { + return err + } + pt := types.ProtocolTypes{} + if err := pt.Set(filters.ProtocolTypes); err != nil { + return err + } + ept := types.ProtocolTypes{} + if err := ept.Set(filters.ExcludeProtocolTypes); err != nil { + return err + } + e.opts.Authors = filters.Authors + e.opts.Tags = filters.Tags + e.opts.ExcludeTags = filters.ExcludeTags + e.opts.IncludeTags = filters.IncludeTags + e.opts.IncludeIds = filters.IDs + e.opts.ExcludeIds = filters.ExcludeIDs + e.opts.Severities = s + e.opts.ExcludeSeverities = es + e.opts.Protocols = pt + e.opts.ExcludeProtocols = ept + e.opts.IncludeConditions = filters.TemplateCondition + return nil + } +} + +// InteractshOpts contains options for interactsh +type InteractshOpts interactsh.Options + +// WithInteractshOptions sets interactsh options +func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithInteractshOptions") + } + optsPtr := &opts + e.interactshOpts = (*interactsh.Options)(optsPtr) + return nil + } +} + +// Concurrency options +type Concurrency struct { + TemplateConcurrency int // number of templates to run concurrently (per host in host-spray mode) + HostConcurrency int // number of hosts to scan concurrently (per template in template-spray mode) + HeadlessHostConcurrency int // number of hosts to scan concurrently for headless templates (per template in template-spray mode) + HeadlessTemplateConcurrency int // number of templates to run concurrently for headless templates (per host in host-spray mode) +} + +// WithConcurrency sets concurrency options +func WithConcurrency(opts Concurrency) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts.TemplateThreads = opts.TemplateConcurrency + e.opts.BulkSize = opts.HostConcurrency + e.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency + e.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency + return nil + } +} + +// WithGlobalRateLimit sets global rate (i.e all hosts combined) limit options +func WithGlobalRateLimit(maxTokens int, duration time.Duration) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.rateLimiter = ratelimit.New(context.Background(), uint(maxTokens), duration) + return nil + } +} + +// HeadlessOpts contains options for headless templates +type HeadlessOpts struct { + PageTimeout int // timeout for page load + ShowBrowser bool + HeadlessOptions []string + UseChrome bool +} + +// EnableHeadless allows execution of headless templates +// *Use With Caution*: Enabling headless mode may open up attack surface due to browser usage +// and can be prone to exploitation by custom unverified templates if not properly configured +func EnableHeadlessWithOpts(hopts *HeadlessOpts) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts.Headless = true + if hopts != nil { + e.opts.HeadlessOptionalArguments = hopts.HeadlessOptions + e.opts.PageTimeout = hopts.PageTimeout + e.opts.ShowBrowser = hopts.ShowBrowser + e.opts.UseInstalledChrome = hopts.UseChrome + } + if engine.MustDisableSandbox() { + gologger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n") + } + browser, err := engine.New(e.opts) + if err != nil { + return err + } + e.executerOpts.Browser = browser + return nil + } +} + +// StatsOptions +type StatsOptions struct { + Interval int + JSON bool + MetricServerPort int +} + +// EnableStats enables Stats collection with defined interval(in sec) and callback +// Note: callback is executed in a separate goroutine +func EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("EnableStatsWithOpts") + } + if opts.Interval == 0 { + opts.Interval = 5 //sec + } + e.opts.StatsInterval = opts.Interval + e.enableStats = true + e.opts.StatsJSON = opts.JSON + e.opts.MetricsPort = opts.MetricServerPort + return nil + } +} + +// VerbosityOptions +type VerbosityOptions struct { + Verbose bool // show verbose output + Silent bool // show only results + Debug bool // show debug output + DebugRequest bool // show request in debug output + DebugResponse bool // show response in debug output + ShowVarDump bool // show variable dumps in output +} + +// WithVerbosity allows setting verbosity options of (internal) nuclei engine +// and does not affect SDK output +func WithVerbosity(opts VerbosityOptions) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithVerbosity") + } + e.opts.Verbose = opts.Verbose + e.opts.Silent = opts.Silent + e.opts.Debug = opts.Debug + e.opts.DebugRequests = opts.DebugRequest + e.opts.DebugResponse = opts.DebugResponse + if opts.ShowVarDump { + vardump.EnableVarDump = true + } + return nil + } +} + +// NetworkConfig contains network config options +// ex: retries , httpx probe , timeout etc +type NetworkConfig struct { + Timeout int // Timeout in seconds + Retries int // Number of retries + LeaveDefaultPorts bool // Leave default ports for http/https + MaxHostError int // Maximum number of host errors to allow before skipping that host + TrackError []string // Adds given errors to max host error watchlist + DisableMaxHostErr bool // Disable max host error optimization (Hosts are not skipped even if they are not responding) +} + +// WithNetworkConfig allows setting network config options +func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithNetworkConfig") + } + e.opts.Timeout = opts.Timeout + e.opts.Retries = opts.Retries + e.opts.LeaveDefaultPorts = opts.LeaveDefaultPorts + e.hostErrCache = hosterrorscache.New(opts.MaxHostError, hosterrorscache.DefaultMaxHostsCount, opts.TrackError) + return nil + } +} + +// WithProxy allows setting proxy options +func WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithProxy") + } + e.opts.Proxy = proxy + e.opts.ProxyInternal = proxyInternalRequests + return nil + } +} + +// WithScanStrategy allows setting scan strategy options +func WithScanStrategy(strategy string) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts.ScanStrategy = strategy + return nil + } +} + +// OutputWriter +type OutputWriter output.Writer + +// UseWriter allows setting custom output writer +// by default a mock writer is used with user defined callback +// if outputWriter is used callback will be ignored +func UseOutputWriter(writer OutputWriter) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("UseOutputWriter") + } + e.customWriter = writer + return nil + } +} + +// StatsWriter +type StatsWriter progress.Progress + +// UseStatsWriter allows setting a custom stats writer +// which can be used to write stats somewhere (ex: send to webserver etc) +func UseStatsWriter(writer StatsWriter) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("UseStatsWriter") + } + e.customProgress = writer + return nil + } +} + +// WithTemplateUpdateCallback allows setting a callback which will be called +// when nuclei templates are outdated +// Note: Nuclei-templates are crucial part of nuclei and using outdated templates or nuclei sdk is not recommended +// as it may cause unexpected results due to compatibility issues +func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithTemplateUpdateCallback") + } + e.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade + e.onUpdateAvailableCallback = callback + return nil + } +} + +// WithSandboxOptions allows setting supported sandbox options +func WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions { + return func(e *NucleiEngine) error { + if e.mode == threadSafe { + return ErrOptionsNotSupported.Msgf("WithSandboxOptions") + } + e.opts.AllowLocalFileAccess = allowLocalFileAccess + e.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess + return nil + } +} diff --git a/v2/lib/example_test.go b/v2/lib/example_test.go new file mode 100644 index 00000000..9794f6c9 --- /dev/null +++ b/v2/lib/example_test.go @@ -0,0 +1,85 @@ +//go:build !race +// +build !race + +package nuclei_test + +import ( + "os" + "testing" + + nuclei "github.com/projectdiscovery/nuclei/v2/lib" + "github.com/remeh/sizedwaitgroup" +) + +// A very simple example on how to use nuclei engine +func ExampleNucleiEngine() { + // create nuclei engine with options + ne, err := nuclei.NewNucleiEngine( + nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"self-signed-ssl"}}), // only run self-signed-ssl template + ) + if err != nil { + panic(err) + } + // load targets and optionally probe non http/https targets + ne.LoadTargets([]string{"scanme.sh"}, false) + // when callback is nil it nuclei will print JSON output to stdout + err = ne.ExecuteWithCallback(nil) + if err != nil { + panic(err) + } + defer ne.Close() + + // Output: + // [self-signed-ssl] scanme.sh:443 +} + +func ExampleThreadSafeNucleiEngine() { + // create nuclei engine with options + ne, err := nuclei.NewThreadSafeNucleiEngine() + if err != nil { + panic(err) + } + // setup sizedWaitgroup to handle concurrency + // here we are using sizedWaitgroup to limit concurrency to 1 + // but can be anything in general + sg := sizedwaitgroup.New(1) + + // scan 1 = run dns templates on scanme.sh + sg.Add() + go func() { + defer sg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, + nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"nameserver-fingerprint"}}), // only run self-signed-ssl template + ) + if err != nil { + panic(err) + } + }() + + // scan 2 = run dns templates on honey.scanme.sh + sg.Add() + go func() { + defer sg.Done() + err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) + if err != nil { + panic(err) + } + }() + + // wait for all scans to finish + sg.Wait() + defer ne.Close() + + // Output: + // [nameserver-fingerprint] scanme.sh +} + +func TestMain(m *testing.M) { + // this file only contains testtables examples https://go.dev/blog/examples + // and actual functionality test are in sdk_test.go + if os.Getenv("GH_ACTION") != "" { + // no need to run this test on github actions + return + } + m.Run() +} diff --git a/v2/lib/helper.go b/v2/lib/helper.go new file mode 100644 index 00000000..281f6799 --- /dev/null +++ b/v2/lib/helper.go @@ -0,0 +1,33 @@ +package nuclei + +import ( + "context" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + uncoverNuclei "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/uncover" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/uncover" +) + +// helper.go file proxy execution of all nuclei functions that are nested deep inside multiple packages +// but are helpful / useful while using nuclei as a library + +// GetTargetsFromUncover returns targets from uncover in given format . +// supported formats are any string with [ip,host,port,url] placeholders +func GetTargetsFromUncover(ctx context.Context, outputFormat string, opts *uncover.Options) (chan string, error) { + return uncoverNuclei.GetTargetsFromUncover(ctx, outputFormat, opts) +} + +// GetTargetsFromTemplateMetadata returns all targets by querying engine metadata (ex: fofo-query,shodan-query) etc from given templates . +// supported formats are any string with [ip,host,port,url] placeholders +func GetTargetsFromTemplateMetadata(ctx context.Context, templates []*templates.Template, outputFormat string, opts *uncover.Options) chan string { + return uncoverNuclei.GetUncoverTargetsFromMetadata(ctx, templates, outputFormat, opts) +} + +// DefaultConfig is instance of default nuclei configs +// any mutations to this config will be reflected in all nuclei instances (saves some config to disk) +var DefaultConfig *config.Config + +func init() { + DefaultConfig = config.DefaultConfig +} diff --git a/v2/lib/multi.go b/v2/lib/multi.go new file mode 100644 index 00000000..ebd85e05 --- /dev/null +++ b/v2/lib/multi.go @@ -0,0 +1,152 @@ +package nuclei + +import ( + "context" + "time" + + "github.com/logrusorgru/aurora" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader" + "github.com/projectdiscovery/nuclei/v2/pkg/core" + "github.com/projectdiscovery/nuclei/v2/pkg/core/inputs" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/parsers" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + "github.com/projectdiscovery/ratelimit" + errorutil "github.com/projectdiscovery/utils/errors" +) + +// unsafeOptions are those nuclei objects/instances/types +// that are required to run nuclei engine but are not thread safe +// hence they are ephemeral and are created on every ExecuteNucleiWithOpts invocation +// in ThreadSafeNucleiEngine +type unsafeOptions struct { + executerOpts protocols.ExecutorOptions + engine *core.Engine +} + +// createEphemeralObjects creates ephemeral nuclei objects/instances/types +func createEphemeralObjects(base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) { + u := &unsafeOptions{} + u.executerOpts = protocols.ExecutorOptions{ + Output: base.customWriter, + Options: opts, + Progress: base.customProgress, + Catalog: base.catalog, + IssuesClient: base.rc, + RateLimiter: base.rateLimiter, + Interactsh: base.interactshClient, + HostErrorsCache: base.hostErrCache, + Colorizer: aurora.NewAurora(true), + ResumeCfg: types.NewResumeCfg(), + } + if opts.RateLimitMinute > 0 { + u.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(opts.RateLimitMinute), time.Minute) + } else if opts.RateLimit > 0 { + u.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(opts.RateLimit), time.Second) + } else { + u.executerOpts.RateLimiter = ratelimit.NewUnlimited(context.Background()) + } + u.engine = core.New(opts) + u.engine.SetExecuterOptions(u.executerOpts) + return u, nil +} + +// ThreadSafeNucleiEngine is a tweaked version of nuclei.Engine whose methods are thread-safe +// and can be used concurrently. Non-thread-safe methods start with Global prefix +type ThreadSafeNucleiEngine struct { + eng *NucleiEngine +} + +// NewThreadSafeNucleiEngine creates a new nuclei engine with given options +// whose methods are thread-safe and can be used concurrently +// Note: Non-thread-safe methods start with Global prefix +func NewThreadSafeNucleiEngine(opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) { + // default options + e := &NucleiEngine{ + opts: types.DefaultOptions(), + mode: threadSafe, + } + for _, option := range opts { + if err := option(e); err != nil { + return nil, err + } + } + if err := e.init(); err != nil { + return nil, err + } + return &ThreadSafeNucleiEngine{eng: e}, nil +} + +// GlobalLoadAllTemplates loads all templates from nuclei-templates repo +// This method will load all templates based on filters given at the time of nuclei engine creation in opts +func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error { + return e.eng.LoadAllTemplates() +} + +// GlobalResultCallback sets a callback function which will be called for each result +func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) { + e.eng.resultCallbacks = []func(*output.ResultEvent){callback} +} + +// ExecuteWithCallback executes templates on targets and calls callback on each result(only if results are found) +// This method can be called concurrently and it will use some global resources but can be runned parllely +// by invoking this method with different options and targets +// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options +func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ...NucleiSDKOptions) error { + baseOpts := *e.eng.opts + tmpEngine := &NucleiEngine{opts: &baseOpts, mode: threadSafe} + for _, option := range opts { + if err := option(tmpEngine); err != nil { + return err + } + } + // create ephemeral nuclei objects/instances/types using base nuclei engine + unsafeOpts, err := createEphemeralObjects(e.eng, tmpEngine.opts) + if err != nil { + return err + } + + // load templates + workflowLoader, err := parsers.NewLoader(&unsafeOpts.executerOpts) + if err != nil { + return errorutil.New("Could not create workflow loader: %s\n", err) + } + unsafeOpts.executerOpts.WorkflowLoader = workflowLoader + + store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)) + if err != nil { + return errorutil.New("Could not create loader client: %s\n", err) + } + store.Load() + + inputProvider := &inputs.SimpleInputProvider{ + Inputs: []*contextargs.MetaInput{}, + } + + // load targets + for _, target := range targets { + inputProvider.Set(target) + } + + if len(store.Templates()) == 0 && len(store.Workflows()) == 0 { + return ErrNoTemplatesAvailable + } + if inputProvider.Count() == 0 { + return ErrNoTargetsAvailable + } + + engine := core.New(tmpEngine.opts) + engine.SetExecuterOptions(unsafeOpts.executerOpts) + + _ = engine.ExecuteScanWithOpts(store.Templates(), inputProvider, false) + + engine.WorkPool().Wait() + return nil +} + +// Close all resources used by nuclei engine +func (e *ThreadSafeNucleiEngine) Close() { + e.eng.Close() +} diff --git a/v2/lib/sdk.go b/v2/lib/sdk.go new file mode 100644 index 00000000..6c1e319b --- /dev/null +++ b/v2/lib/sdk.go @@ -0,0 +1,180 @@ +package nuclei + +import ( + "bufio" + "io" + + "github.com/projectdiscovery/httpx/common/httpx" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader" + "github.com/projectdiscovery/nuclei/v2/pkg/core" + "github.com/projectdiscovery/nuclei/v2/pkg/core/inputs" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/parsers" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" + "github.com/projectdiscovery/nuclei/v2/pkg/reporting" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + "github.com/projectdiscovery/ratelimit" + "github.com/projectdiscovery/retryablehttp-go" + errorutil "github.com/projectdiscovery/utils/errors" +) + +// NucleiSDKOptions contains options for nuclei SDK +type NucleiSDKOptions func(e *NucleiEngine) error + +var ( + // ErrNotImplemented is returned when a feature is not implemented + ErrNotImplemented = errorutil.New("Not implemented") + // ErrNoTemplatesAvailable is returned when no templates are available to execute + ErrNoTemplatesAvailable = errorutil.New("No templates available") + // ErrNoTargetsAvailable is returned when no targets are available to scan + ErrNoTargetsAvailable = errorutil.New("No targets available") + // ErrOptionsNotSupported is returned when an option is not supported in thread safe mode + ErrOptionsNotSupported = errorutil.NewWithFmt("Option %v not supported in thread safe mode") +) + +type engineMode uint + +const ( + singleInstance engineMode = iota + threadSafe +) + +// NucleiEngine is the Engine/Client for nuclei which +// runs scans using templates and returns results +type NucleiEngine struct { + // user options + resultCallbacks []func(event *output.ResultEvent) + onFailureCallback func(event *output.InternalEvent) + disableTemplatesAutoUpgrade bool + enableStats bool + onUpdateAvailableCallback func(newVersion string) + + // ready-status fields + templatesLoaded bool + + // unexported core fields + interactshClient *interactsh.Client + catalog *disk.DiskCatalog + rateLimiter *ratelimit.Limiter + store *loader.Store + httpxClient *httpx.HTTPX + inputProvider *inputs.SimpleInputProvider + engine *core.Engine + mode engineMode + browserInstance *engine.Browser + httpClient *retryablehttp.Client + + // unexported meta options + opts *types.Options + interactshOpts *interactsh.Options + hostErrCache *hosterrorscache.Cache + customWriter output.Writer + customProgress progress.Progress + rc reporting.Client + executerOpts protocols.ExecutorOptions +} + +// LoadAllTemplates loads all nuclei template based on given options +func (e *NucleiEngine) LoadAllTemplates() error { + workflowLoader, err := parsers.NewLoader(&e.executerOpts) + if err != nil { + return errorutil.New("Could not create workflow loader: %s\n", err) + } + e.executerOpts.WorkflowLoader = workflowLoader + + e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts)) + if err != nil { + return errorutil.New("Could not create loader client: %s\n", err) + } + e.store.Load() + return nil +} + +// GetTemplates returns all nuclei templates that are loaded +func (e *NucleiEngine) GetTemplates() []*templates.Template { + if !e.templatesLoaded { + _ = e.LoadAllTemplates() + } + return e.store.Templates() +} + +// LoadTargets(urls/domains/ips only) adds targets to the nuclei engine +func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) { + for _, target := range targets { + if probeNonHttp { + e.inputProvider.SetWithProbe(target, e.httpxClient) + } else { + e.inputProvider.Set(target) + } + } +} + +// LoadTargetsFromReader adds targets(urls/domains/ips only) from reader to the nuclei engine +func (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool) { + buff := bufio.NewScanner(reader) + for buff.Scan() { + if probeNonHttp { + e.inputProvider.SetWithProbe(buff.Text(), e.httpxClient) + } else { + e.inputProvider.Set(buff.Text()) + } + } +} + +// Close all resources used by nuclei engine +func (e *NucleiEngine) Close() { + e.interactshClient.Close() + e.rc.Close() + e.customWriter.Close() + e.hostErrCache.Close() + e.executerOpts.RateLimiter.Stop() +} + +// ExecuteWithCallback executes templates on targets and calls callback on each result(only if results are found) +func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error { + if !e.templatesLoaded { + _ = e.LoadAllTemplates() + } + if len(e.store.Templates()) == 0 && len(e.store.Workflows()) == 0 { + return ErrNoTemplatesAvailable + } + if e.inputProvider.Count() == 0 { + return ErrNoTargetsAvailable + } + + filtered := []func(event *output.ResultEvent){} + for _, callback := range callback { + if callback != nil { + filtered = append(filtered, callback) + } + } + e.resultCallbacks = append(e.resultCallbacks, filtered...) + + _ = e.engine.ExecuteScanWithOpts(e.store.Templates(), e.inputProvider, false) + defer e.engine.WorkPool().Wait() + return nil +} + +// NewNucleiEngine creates a new nuclei engine instance +func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) { + // default options + e := &NucleiEngine{ + opts: types.DefaultOptions(), + mode: singleInstance, + } + for _, option := range options { + if err := option(e); err != nil { + return nil, err + } + } + if err := e.init(); err != nil { + return nil, err + } + return e, nil +} diff --git a/v2/lib/sdk_private.go b/v2/lib/sdk_private.go new file mode 100644 index 00000000..628cdc3b --- /dev/null +++ b/v2/lib/sdk_private.go @@ -0,0 +1,197 @@ +package nuclei + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/logrusorgru/aurora" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" + "github.com/projectdiscovery/httpx/common/httpx" + "github.com/projectdiscovery/nuclei/v2/internal/installer" + "github.com/projectdiscovery/nuclei/v2/internal/runner" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/core" + "github.com/projectdiscovery/nuclei/v2/pkg/core/inputs" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool" + "github.com/projectdiscovery/nuclei/v2/pkg/reporting" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + "github.com/projectdiscovery/ratelimit" +) + +// applyRequiredDefaults to options +func (e *NucleiEngine) applyRequiredDefaults() { + if e.customWriter == nil { + mockoutput := testutils.NewMockOutputWriter() + mockoutput.WriteCallback = func(event *output.ResultEvent) { + if len(e.resultCallbacks) > 0 { + for _, callback := range e.resultCallbacks { + if callback != nil { + callback(event) + } + } + return + } + sb := strings.Builder{} + sb.WriteString(fmt.Sprintf("[%v] ", event.TemplateID)) + if event.Matched != "" { + sb.WriteString(event.Matched) + } else { + sb.WriteString(event.Host) + } + fmt.Println(sb.String()) + } + if e.onFailureCallback != nil { + mockoutput.FailureCallback = e.onFailureCallback + } + e.customWriter = mockoutput + } + if e.customProgress == nil { + e.customProgress = &testutils.MockProgressClient{} + } + if e.hostErrCache == nil { + e.hostErrCache = hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil) + } + // setup interactsh + if e.interactshOpts != nil { + e.interactshOpts.Output = e.customWriter + e.interactshOpts.Progress = e.customProgress + } else { + e.interactshOpts = interactsh.DefaultOptions(e.customWriter, e.rc, e.customProgress) + } + if e.rateLimiter == nil { + e.rateLimiter = ratelimit.New(context.Background(), 150, time.Second) + } + // these templates are known to have weak matchers + // and idea is to disable them to avoid false positives + e.opts.ExcludeTags = config.ReadIgnoreFile().Tags + + e.inputProvider = &inputs.SimpleInputProvider{ + Inputs: []*contextargs.MetaInput{}, + } +} + +// init +func (e *NucleiEngine) init() error { + if e.opts.Verbose { + gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) + } else if e.opts.Debug { + gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) + } else if e.opts.Silent { + gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + } + + if err := runner.ValidateOptions(e.opts); err != nil { + return err + } + + if e.opts.ProxyInternal && types.ProxyURL != "" || types.ProxySocksURL != "" { + httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{}) + if err != nil { + return err + } + e.httpClient = httpclient + } + + _ = protocolstate.Init(e.opts) + _ = protocolinit.Init(e.opts) + e.applyRequiredDefaults() + var err error + + // setup progressbar + if e.enableStats { + progressInstance, progressErr := progress.NewStatsTicker(e.opts.StatsInterval, e.enableStats, e.opts.StatsJSON, false, e.opts.MetricsPort) + if progressErr != nil { + return err + } + e.customProgress = progressInstance + e.interactshOpts.Progress = progressInstance + } + + if err := reporting.CreateConfigIfNotExists(); err != nil { + return err + } + // we don't support reporting config in sdk mode + if e.rc, err = reporting.New(&reporting.Options{}, ""); err != nil { + return err + } + e.interactshOpts.IssuesClient = e.rc + if e.httpClient != nil { + e.interactshOpts.HTTPClient = e.httpClient + } + if e.interactshClient, err = interactsh.New(e.interactshOpts); err != nil { + return err + } + + e.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) + + e.executerOpts = protocols.ExecutorOptions{ + Output: e.customWriter, + Options: e.opts, + Progress: e.customProgress, + Catalog: e.catalog, + IssuesClient: e.rc, + RateLimiter: e.rateLimiter, + Interactsh: e.interactshClient, + HostErrorsCache: e.hostErrCache, + Colorizer: aurora.NewAurora(true), + ResumeCfg: types.NewResumeCfg(), + Browser: e.browserInstance, + } + + if e.opts.RateLimitMinute > 0 { + e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimitMinute), time.Minute) + } else if e.opts.RateLimit > 0 { + e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimit), time.Second) + } else { + e.executerOpts.RateLimiter = ratelimit.NewUnlimited(context.Background()) + } + + e.engine = core.New(e.opts) + e.engine.SetExecuterOptions(e.executerOpts) + + httpxOptions := httpx.DefaultOptions + httpxOptions.Timeout = 5 * time.Second + if e.httpxClient, err = httpx.New(&httpxOptions); err != nil { + return err + } + + // Only Happens once regardless how many times this function is called + // This will update ignore file to filter out templates with weak matchers to avoid false positives + // and also upgrade templates to latest version if available + installer.NucleiSDKVersionCheck() + + return e.processUpdateCheckResults() +} + +type syncOnce struct { + sync.Once +} + +var updateCheckInstance = &syncOnce{} + +// processUpdateCheckResults processes update check results +func (e *NucleiEngine) processUpdateCheckResults() error { + var err error + updateCheckInstance.Do(func() { + if e.onUpdateAvailableCallback != nil { + e.onUpdateAvailableCallback(config.DefaultConfig.LatestNucleiTemplatesVersion) + } + tm := installer.TemplateManager{} + err = tm.UpdateIfOutdated() + }) + return err +} diff --git a/v2/lib/sdk_test.go b/v2/lib/sdk_test.go new file mode 100644 index 00000000..44ed81aa --- /dev/null +++ b/v2/lib/sdk_test.go @@ -0,0 +1,60 @@ +package nuclei_test + +import ( + "testing" + + nuclei "github.com/projectdiscovery/nuclei/v2/lib" + "github.com/stretchr/testify/require" +) + +func TestSimpleNuclei(t *testing.T) { + ne, err := nuclei.NewNucleiEngine( + nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), + nuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}), + ) + require.Nil(t, err) + ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here + // when callback is nil it nuclei will print JSON output to stdout + err = ne.ExecuteWithCallback(nil) + require.Nil(t, err) + defer ne.Close() +} + +func TestSimpleNucleiRemote(t *testing.T) { + ne, err := nuclei.NewNucleiEngine( + nuclei.WithTemplatesOrWorkflows( + nuclei.TemplateSources{ + RemoteTemplates: []string{"https://templates.nuclei.sh/public/nameserver-fingerprint.yaml"}, + }, + ), + ) + require.Nil(t, err) + ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here + err = ne.LoadAllTemplates() + require.Nil(t, err, "could not load templates") + // when callback is nil it nuclei will print JSON output to stdout + err = ne.ExecuteWithCallback(nil) + require.Nil(t, err) + defer ne.Close() +} + +func TestThreadSafeNuclei(t *testing.T) { + // create nuclei engine with options + ne, err := nuclei.NewThreadSafeNucleiEngine() + require.Nil(t, err) + + // scan 1 = run dns templates on scanme.sh + t.Run("scanme.sh", func(t *testing.T) { + err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) + require.Nil(t, err) + }) + + // scan 2 = run dns templates on honey.scanme.sh + t.Run("honey.scanme.sh", func(t *testing.T) { + err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) + require.Nil(t, err) + }) + + // wait for all scans to finish + defer ne.Close() +} diff --git a/v2/nuclei.crt b/v2/nuclei.crt new file mode 100644 index 00000000..f553377c --- /dev/null +++ b/v2/nuclei.crt @@ -0,0 +1,11 @@ +-----BEGIN PD NUCLEI USER CERTIFICATE----- +MIIBgDCCASWgAwIBAgIEZSUZ3jAKBggqhkjOPQQDAjAsMSowKAYDVQQDEyFwcm9q +ZWN0ZGlzY292ZXJ5L251Y2xlaS10ZW1wbGF0ZXMwHhcNMjMxMDEwMDkzMTEwWhcN +MjcxMDA5MDkzMTEwWjAsMSowKAYDVQQDEyFwcm9qZWN0ZGlzY292ZXJ5L251Y2xl +aS10ZW1wbGF0ZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASTaiE41H7LWudF +SMCfnqguQMwEte7dz/FRfK2lmezE02w+I2VwcS3j5cPwNaqYRAJkQhk6+7li0GpG +9fb11Fs2ozUwMzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNJADBGAiEAhFsWwLDcWks3RUv3ujCs +4V1reu6KL+kELrCCQWu5FiUCIQDZbtqL30GPGYaPSpVmd6BKrZDBOfUVBsoCS7pS +q3JLHQ== +-----END PD NUCLEI USER CERTIFICATE----- \ No newline at end of file diff --git a/v2/pkg/catalog/config/constants.go b/v2/pkg/catalog/config/constants.go index fd012b47..8f01c918 100644 --- a/v2/pkg/catalog/config/constants.go +++ b/v2/pkg/catalog/config/constants.go @@ -17,12 +17,15 @@ const ( CLIConfigFileName = "config.yaml" ReportingConfigFilename = "reporting-config.yaml" // Version is the current version of nuclei - Version = `v2.9.15` + Version = `v3.0.0-dev` // Directory Names of custom templates CustomS3TemplatesDirName = "s3" CustomGitHubTemplatesDirName = "github" CustomAzureTemplatesDirName = "azure" CustomGitLabTemplatesDirName = "gitlab" + BinaryName = "nuclei" + FallbackConfigFolderName = ".nuclei-config" + NucleiConfigDirEnv = "NUCLEI_CONFIG_DIR" ) // IsOutdatedVersion compares two versions and returns true diff --git a/v2/pkg/catalog/config/nucleiconfig.go b/v2/pkg/catalog/config/nucleiconfig.go index 7dafbb8d..fb4be387 100644 --- a/v2/pkg/catalog/config/nucleiconfig.go +++ b/v2/pkg/catalog/config/nucleiconfig.go @@ -5,11 +5,14 @@ import ( "crypto/md5" "encoding/json" "fmt" + "log" "os" "path/filepath" "strings" + "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/utils/env" errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" @@ -31,6 +34,7 @@ type Config struct { TemplateVersion string `json:"nuclei-templates-version,omitempty"` NucleiIgnoreHash string `json:"nuclei-ignore-hash,omitempty"` + LogAllEvents bool `json:"-"` // when enabled logs all events (more than verbose) // LatestXXX are not meant to be used directly and is used as // local cache of nuclei version check endpoint @@ -68,6 +72,12 @@ func (c *Config) WriteVersionCheckData(ignorehash, nucleiVersion, templatesVersi return nil } +// GetTemplateDir returns the nuclei templates directory absolute path +func (c *Config) GetTemplateDir() string { + val, _ := filepath.Abs(c.TemplatesDirectory) + return val +} + // DisableUpdateCheck disables update check and template updates func (c *Config) DisableUpdateCheck() { c.disableUpdates = true @@ -109,6 +119,11 @@ func (c *Config) GetConfigDir() string { return c.configDir } +// GetKeysDir returns the nuclei signer keys directory +func (c *Config) GetKeysDir() string { + return filepath.Join(c.configDir, "keys") +} + // GetAllCustomTemplateDirs returns all custom template directories func (c *Config) GetAllCustomTemplateDirs() []string { return []string{c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory} @@ -158,6 +173,14 @@ func (c *Config) GetNewAdditions() []string { return arr } +// GetCacheDir returns the nuclei cache directory +// with new version of nuclei cache directory is changed +// instead of saving resume files in nuclei config directory +// they are saved in nuclei cache directory +func (c *Config) GetCacheDir() string { + return folderutil.AppCacheDirOrDefault(".nuclei-cache", BinaryName) +} + // SetConfigDir sets the nuclei configuration directory // and appropriate changes are made to the config func (c *Config) SetConfigDir(dir string) { @@ -276,15 +299,24 @@ func (c *Config) copyIgnoreFile() { } ignoreFilePath := c.GetIgnoreFilePath() if !fileutil.FileExists(ignoreFilePath) { - // copy ignore file - if err := fileutil.CopyFile(filepath.Join(getDefaultConfigDir(), NucleiIgnoreFileName), ignoreFilePath); err != nil { + // copy ignore file from default config directory + if err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil { gologger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err) } } } func init() { - ConfigDir := getDefaultConfigDir() + // first attempt to migrate all files from old config directory to new config directory + goflags.AttemptConfigMigration() // regardless how many times this is called it will only migrate once based on condition + + ConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName) + + if cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != "" { + ConfigDir = cfgDir + } + + // create config directory if not exists if !fileutil.FolderExists(ConfigDir) { if err := fileutil.CreateFolder(ConfigDir); err != nil { gologger.Error().Msgf("failed to create config directory at %v got: %s", ConfigDir, err) @@ -294,6 +326,14 @@ func init() { homeDir: folderutil.HomeDirOrDefault(""), configDir: ConfigDir, } + + // when enabled will log events in more verbosity than -v or -debug + // ex: N templates are excluded + // with this switch enabled nuclei will print details of above N templates + if value := env.GetEnvOrDefault("NUCLEI_LOG_ALL", false); value { + DefaultConfig.LogAllEvents = true + } + // try to read config from file if err := DefaultConfig.ReadTemplatesConfig(); err != nil { gologger.Verbose().Msgf("config file not found, creating new config file at %s", DefaultConfig.getTemplatesConfigFilePath()) @@ -303,6 +343,9 @@ func init() { gologger.Error().Msgf("failed to write config file at %s got: %s", DefaultConfig.getTemplatesConfigFilePath(), err) } } + // attempt to migrate resume files + // this also happens once regardless of how many times this is called + migrateResumeFiles() // Loads/updates paths of custom templates // Note: custom templates paths should not be updated in config file // and even if it is changed we don't follow it since it is not expected behavior @@ -310,22 +353,67 @@ func init() { DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory) } -func getDefaultConfigDir() string { - // Review Needed: Earlier a dependency was used to locate home dir - // i.e "github.com/mitchellh/go-homedir" not sure if it is needed - // Even if such case exists it should be abstracted via below function call in utils/folder - homedir := folderutil.HomeDirOrDefault("") - // TBD: we should probably stick to specification and use config directories provided by distro - // instead of manually creating one since $HOME/.config/ is config directory of Linux desktops - // Ref: https://pkg.go.dev/os#UserConfigDir - // some distros like NixOS or others have totally different config directories this causes issues for us (since we are not using os.UserConfigDir) - userCfgDir := filepath.Join(homedir, ".config") - return filepath.Join(userCfgDir, "nuclei") -} - // Add Default Config adds default when .templates-config.json file is not present func applyDefaultConfig() { DefaultConfig.TemplatesDirectory = filepath.Join(DefaultConfig.homeDir, NucleiTemplatesDirName) // updates all necessary paths DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory) } + +func migrateResumeFiles() { + // attempt to migrate old resume files to new directory structure + // after migration has been done in goflags + oldResumeDir := DefaultConfig.GetConfigDir() + // migrate old resume file to new directory structure + if !fileutil.FileOrFolderExists(DefaultConfig.GetCacheDir()) && fileutil.FileOrFolderExists(oldResumeDir) { + // this means new cache dir doesn't exist, so we need to migrate + // first check if old resume file exists if not then no need to migrate + exists := false + files, err := os.ReadDir(oldResumeDir) + if err != nil { + // log silently + log.Printf("could not read old resume dir: %s\n", err) + return + } + for _, file := range files { + if strings.HasSuffix(file.Name(), ".cfg") { + exists = true + break + } + } + if !exists { + // no need to migrate + return + } + + // create new cache dir + err = os.MkdirAll(DefaultConfig.GetCacheDir(), os.ModePerm) + if err != nil { + // log silently + log.Printf("could not create new cache dir: %s\n", err) + return + } + err = filepath.WalkDir(oldResumeDir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".cfg") { + return nil + } + err = os.Rename(path, filepath.Join(DefaultConfig.GetCacheDir(), filepath.Base(path))) + if err != nil { + return err + } + return nil + }) + if err != nil { + // log silently + log.Printf("could not migrate old resume files: %s\n", err) + return + } + + } +} diff --git a/v2/pkg/catalog/disk/find.go b/v2/pkg/catalog/disk/find.go index 98b8cf5b..85abe328 100644 --- a/v2/pkg/catalog/disk/find.go +++ b/v2/pkg/catalog/disk/find.go @@ -81,6 +81,9 @@ func (c *DiskCatalog) GetTemplatePath(target string) ([]string, error) { // try to handle deprecated template paths absPath := BackwardsCompatiblePaths(c.templatesDirectory, target) if absPath != target && strings.TrimPrefix(absPath, c.templatesDirectory+string(filepath.Separator)) != target { + if config.DefaultConfig.LogAllEvents { + gologger.DefaultLogger.Print().Msgf("[%v] requested Template path %s is deprecated, please update to %s\n", aurora.Yellow("WRN").String(), target, absPath) + } deprecatedPathsCounter++ } diff --git a/v2/pkg/catalog/disk/path.go b/v2/pkg/catalog/disk/path.go index 18673db2..0280641c 100644 --- a/v2/pkg/catalog/disk/path.go +++ b/v2/pkg/catalog/disk/path.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/pkg/errors" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" fileutil "github.com/projectdiscovery/utils/file" urlutil "github.com/projectdiscovery/utils/url" ) @@ -37,12 +38,11 @@ func (c *DiskCatalog) ResolvePath(templateName, second string) (string, error) { return potentialPath, nil } - if c.templatesDirectory != "" { - templatePath := filepath.Join(c.templatesDirectory, templateName) - if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination { - return potentialPath, nil - } + templatePath = filepath.Join(config.DefaultConfig.GetTemplateDir(), templateName) + if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination { + return potentialPath, nil } + return "", fmt.Errorf("no such path found: %s", templateName) } @@ -50,7 +50,7 @@ var errNoValidCombination = errors.New("no valid combination found") // tryResolve attempts to load locate the target by iterating across all the folders tree func (c *DiskCatalog) tryResolve(fullPath string) (string, error) { - if _, err := os.Stat(fullPath); !os.IsNotExist(err) { + if fileutil.FileExists(fullPath) { return fullPath, nil } return "", errNoValidCombination diff --git a/v2/pkg/catalog/loader/loader.go b/v2/pkg/catalog/loader/loader.go index f633cbd7..e1967fa0 100644 --- a/v2/pkg/catalog/loader/loader.go +++ b/v2/pkg/catalog/loader/loader.go @@ -8,9 +8,11 @@ import ( "sort" "strings" + "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/catalog" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" cfg "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" @@ -32,6 +34,10 @@ const ( httpsPrefix = "https://" ) +var ( + TrustedTemplateDomains = []string{"templates.nuclei.sh", "cloud.projectdiscovery.io"} +) + // Config contains the configuration options for the loader type Config struct { Templates []string @@ -100,6 +106,7 @@ func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts pro Catalog: catalog, ExecutorOptions: executerOpts, } + loaderConfig.RemoteTemplateDomainList = append(loaderConfig.RemoteTemplateDomainList, TrustedTemplateDomains...) return &loaderConfig } @@ -121,6 +128,7 @@ func New(config *Config) (*Store, error) { if err != nil { return nil, err } + // Create a tag filter based on provided configuration store := &Store{ config: config, @@ -388,13 +396,30 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ gologger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) } else if parsed != nil { if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless { - gologger.Warning().Msgf("Headless flag is required for headless template %s\n", templatePath) + // donot include headless template in final list if headless flag is not set + stats.Increment(parsers.HeadlessFlagWarningStats) + if config.DefaultConfig.LogAllEvents { + gologger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) + } + } else if len(parsed.RequestsCode) > 0 && !parsed.Verified && len(parsed.Workflows) == 0 { + // donot include unverified 'Code' protocol custom template in final list + stats.Increment(parsers.UnsignedWarning) + if config.DefaultConfig.LogAllEvents { + gologger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath) + } } else { loadedTemplates = append(loadedTemplates, parsed) } } } if err != nil { + if strings.Contains(err.Error(), filter.ErrExcluded.Error()) { + stats.Increment(parsers.TemplatesExecutedStats) + if config.DefaultConfig.LogAllEvents { + gologger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) + } + continue + } gologger.Warning().Msg(err.Error()) } } diff --git a/v2/pkg/core/execute_options.go b/v2/pkg/core/execute_options.go index 4654e792..6e890f7b 100644 --- a/v2/pkg/core/execute_options.go +++ b/v2/pkg/core/execute_options.go @@ -6,6 +6,7 @@ import ( "github.com/remeh/sizedwaitgroup" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/templates" @@ -34,13 +35,36 @@ func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target results := &atomic.Bool{} selfcontainedWg := &sync.WaitGroup{} + totalReqBeforeCluster := getRequestCount(templatesList) * int(target.Count()) + + // attempt to cluster templates if noCluster is false var finalTemplates []*templates.Template + clusterCount := 0 if !noCluster { - finalTemplates, _ = templates.ClusterTemplates(templatesList, e.executerOpts) + finalTemplates, clusterCount = templates.ClusterTemplates(templatesList, e.executerOpts) } else { finalTemplates = templatesList } + totalReqAfterClustering := getRequestCount(finalTemplates) * int(target.Count()) + + if !noCluster && totalReqAfterClustering < totalReqBeforeCluster { + gologger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) + } + + // 0 matches means no templates were found in the directory + if len(finalTemplates) == 0 { + return &atomic.Bool{} + } + + if e.executerOpts.Progress != nil { + // Notes: + // workflow requests are not counted as they can be conditional + // templateList count is user requested templates count (before clustering) + // totalReqAfterClustering is total requests count after clustering + e.executerOpts.Progress.Init(target.Count(), len(templatesList), int64(totalReqAfterClustering)) + } + if stringsutil.EqualFoldAny(e.options.ScanStrategy, scanstrategy.Auto.String(), "") { // TODO: this is only a placeholder, auto scan strategy should choose scan strategy // based on no of hosts , templates , stream and other optimization parameters @@ -122,3 +146,17 @@ func (e *Engine) executeHostSpray(templatesList []*templates.Template, target In wp.Wait() return results } + +// returns total requests count +func getRequestCount(templates []*templates.Template) int { + count := 0 + for _, template := range templates { + // ignore requests in workflows as total requests in workflow + // depends on what templates will be called in workflow + if len(template.Workflows) > 0 { + continue + } + count += template.TotalRequests + } + return count +} diff --git a/v2/pkg/core/inputs/hybrid/hmap_test.go b/v2/pkg/core/inputs/hybrid/hmap_test.go index f9e9deeb..60f12972 100644 --- a/v2/pkg/core/inputs/hybrid/hmap_test.go +++ b/v2/pkg/core/inputs/hybrid/hmap_test.go @@ -155,15 +155,14 @@ func Test_expandASNInputValue(t *testing.T) { asn string expectedOutputFile string }{ - // { - // asn: "AS14421", - // expectedOutputFile: "tests/AS14421.txt", - // }, - // skipping since there is a issue with ASN lookup for AS134029 - // { - // asn: "AS134029", - // expectedOutputFile: "tests/AS134029.txt", - // }, + { + asn: "AS14421", + expectedOutputFile: "tests/AS14421.txt", + }, + { + asn: "AS134029", + expectedOutputFile: "tests/AS134029.txt", + }, } for _, tt := range tests { hm, err := hybrid.New(hybrid.DefaultDiskOptions) diff --git a/v2/pkg/core/workflow_execute_test.go b/v2/pkg/core/workflow_execute_test.go index fc617c57..5b2db134 100644 --- a/v2/pkg/core/workflow_execute_test.go +++ b/v2/pkg/core/workflow_execute_test.go @@ -15,7 +15,7 @@ import ( ) func TestWorkflowsSimple(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ @@ -29,7 +29,7 @@ func TestWorkflowsSimple(t *testing.T) { } func TestWorkflowsSimpleMultiple(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ @@ -54,7 +54,7 @@ func TestWorkflowsSimpleMultiple(t *testing.T) { } func TestWorkflowsSubtemplates(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ @@ -80,7 +80,7 @@ func TestWorkflowsSubtemplates(t *testing.T) { } func TestWorkflowsSubtemplatesNoMatch(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ @@ -104,7 +104,7 @@ func TestWorkflowsSubtemplatesNoMatch(t *testing.T) { } func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ @@ -133,7 +133,7 @@ func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) { } func TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) { - progressBar, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ diff --git a/v2/pkg/js/CONTRIBUTE.md b/v2/pkg/js/CONTRIBUTE.md new file mode 100644 index 00000000..3608b173 --- /dev/null +++ b/v2/pkg/js/CONTRIBUTE.md @@ -0,0 +1,73 @@ +# JS Contribution Guide + +The JS layer provides a mechanism to add scriptability into the Nuclei Engine. The `pkg/js` directory contains the implementation of the JS runtime in Nuclei. This document provides a guide to adding new libraries, extending existing ones and other types of contributions. + +## First step + +The Very First before making any type of contribution to javascript runtime in nuclei is taking a look at [design.md](./DESIGN.md) to understand spread out design of nuclei javascript runtime. + + +## Documentation/Typo Contribution + +Most of Javascript API Reference documentation is auto-generated with help of code-generation and [jsdocgen](./devtools/jsdocgen/README.md) and hence any type of documentation contribution are always welcome and can be done by editing [javscript jsdoc](./generated/js/) files + + +## Improving Existing Libraries(aka node_modules) + +Improving existing libraries includes adding new functions, types, fixing bugs etc to any of the existing libraries in [libs](./libs/) directory. This is very easy to achieve and can be done by following steps below + +1. Do suggested changes in targeted package in [libs](./libs/) directory +2. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation +3. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory + +## Adding New Libraries(aka node_modules) + +Libraries/node_modules represent adding new protocol or something similar and should not include helper functions or types/objects .Adding new libraries requires few more steps than improving existing libraries and can be done by following steps below + +1. Refer any existing library in [libs](./libs/) directory to understand style and structure of node_modules +2. Create new package in [libs](./libs/) directory with suggested protocol / library +3. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation +4. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory +5. Import newly created library with '_' import in [compiler](./compiler/compiler.go) + + +## Adding Helper Objects/Types/Functions + +Helper objects/types/functions can simply be understood as javascript utils to simplify writing javscript and reduce code duplication in javascript templates. Helper functions/objects are divided into two categories + +### javascript based helpers + +javascript based helpers are written in javascript and are available in javascript runtime by default without needing to import any module. These are located in [global/js](./global/js/) directory and are exported using [exports.js](./global/exports.js) file. + + +### go based helpers + +go based helpers are written in go and can import any go library if required. Minimal/Simple helper functions can be directly added using `runtime.Set("function_name", function)` in [global/scripts.go](./global/scripts.go) file. For more complex helpers, a new package can be created in [libs](./libs/) directory and can be imported in [global/scripts.go](./global/scripts.go) file. Refer to existing implementations in [globals](./global/) directory for more details. + + +### Updating / Publishing Docs + +Javscript Protocol Documentation is auto-generated using [jsdoc] and is hosted at [js-proto-docs](https://projectdiscovery.github.io/js-proto-docs/). To update documentation, please follow steps mentioned at [projectdiscovery/js-proto-docs](https://github.com/projectdiscovery/js-proto-docs) + + +### Go Code Guidelines + +1. Always use 'protocolstate.Dialer' (i.e fastdialer) to dial connections instead of net.Dial this has many benefits along with proxy support , **network policy** usage (i.e local network access can be disabled from cli) +2. When usage of 'protocolstate.Dialer' is not possible due to some reason ex: imported library does not accept dialer etc then validate host using 'protocolstate.IsHostAllowed' before dialing connection. +```go + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } +``` +3. Keep exported package clean. Do not keep unncessary global exports which the consumer of the API doesn't need to know about. Keep only user-exposed API public. +4. Use timeouts and context cancellation when calling Network related stuff. Also make sure to close your connections or provide a mechanism to the user of the API to do so. +5. Always try to return single types from inside javascript with an error like `(IsRDP, error)` instead of returning multiple values `(name, version string, err error)`. The second one will get converted to an array is much harder for consumers to deal with. Instead, try to return `Structures` which will be accessible natively. + + +### Javascript Code Guidelines + +1. Catch exceptions using `try/catch` blocks and handle errors gracefully, showing useful information. By default, the implementation returns a Go error on a unhandled exception along with stack trace in debug mode. +2. Use `let`/`cost` instead of `var` to declare variables. +3. Keep the global scope clean. The VMs are not shared so do not rely on VM state. +4. Use functions to divide the code and keep the implementation clean. \ No newline at end of file diff --git a/v2/pkg/js/DESIGN.md b/v2/pkg/js/DESIGN.md new file mode 100644 index 00000000..516ae0c2 --- /dev/null +++ b/v2/pkg/js/DESIGN.md @@ -0,0 +1,47 @@ +# javascript protocol design + +javascript protocol is implemented using `goja`(pure go javascript VM) and overall logic/design of its usage is split into multiple packages/directories + +## [api_reference](./api_reference/) + +api_reference contains a static site generated using `jsdoc` . It contains documentation for all the exposed functions and types in javascript protocol. + +## [compiler](./compiler/) + +compiler contains abstracted logic for compiling and executing javascript code. It also handles loading javascript aka node modules , adding builtin / global types and functions etc. + +## [devtools](./devtools/README.md) + +devtools contains development related tools to automate booring tasks like generating bindings, adding jsdoc comments, generating api reference etc. + +## [generated](./generated/README.md) + +generated contains two types of generated code + +### [- generated/go](./generated/go/) + +generated/go contains actual bindings for native go packages using `goja` this involves exposing libraries,functions and types written in go to javascript. + +### [- generated/js](./generated/js/) + +generated/js contains a visual representation of all exposed functions and types in javascript minus the actual implementation . it is meant to be used as a reference for developers and generating api reference. + +## [global](./global/) + +global (or builtin) contains all builtin types and functions that are by default available in javascript runtime without needing to import any module using 'require' keyword. Its split into 2 sections + +### [- global/js](./global/js/) + +global/js contains javascript code and it acts more like a javascript library and contains functions / types written in javascript itself and exported using [exports.js](./global/exports.js) + +### [- global/scripts.go](./global/scripts.go) + +global/scripts.go contains declaration and implementation of functions written in go and are made available in javascript runtime. It also contains loading javascript based global functions this is done by executing javascript code in every vm instance. + +## [gojs](./gojs/) + +gojs contain minimalistic types and interfaces used to register packages written in go as node_modules in javascript runtime. + +## [libs](./libs/) + +libs contains all go native packages that contain **actual** implementation of all the functions and types that are exposed to javascript runtime. \ No newline at end of file diff --git a/v2/pkg/js/THANKS.md b/v2/pkg/js/THANKS.md new file mode 100644 index 00000000..55619040 --- /dev/null +++ b/v2/pkg/js/THANKS.md @@ -0,0 +1,9 @@ +# THANKS + +- https://github.com/dop251/goja - Pure Go Javascript VM used by nuclei JS layer. +- https://github.com/gogap/gojs-tool - Inspiration for code generation used in JS Libraries addition. +- https://github.com/ropnop/kerbrute - Kerberos Module of JS layer +- https://github.com/praetorian-inc/fingerprintx - A lot of Network Protocol fingerprinting functionality is used from `fingerprintx` package. +- https://github.com/zmap/zgrab2 - Used for SMB and SSH protocol handshake Metadata gathering. + +A lot of other Go based libraries are used in the javascript layer. Thanks goes to the creators and maintainers. \ No newline at end of file diff --git a/v2/pkg/js/compiler/compiler.go b/v2/pkg/js/compiler/compiler.go new file mode 100644 index 00000000..9e0e5cac --- /dev/null +++ b/v2/pkg/js/compiler/compiler.go @@ -0,0 +1,218 @@ +// Package compiler provides a compiler for the goja runtime. +package compiler + +import ( + "runtime/debug" + + "github.com/dop251/goja" + "github.com/dop251/goja/parser" + "github.com/dop251/goja_nodejs/console" + "github.com/dop251/goja_nodejs/require" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + + "github.com/projectdiscovery/gologger" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libbytes" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libfs" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libikev2" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libkerberos" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libldap" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libmssql" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libmysql" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libnet" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/liboracle" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libpop3" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libpostgres" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/librdp" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libredis" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/librsync" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libsmb" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libsmtp" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libssh" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libstructs" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libtelnet" + _ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libvnc" + "github.com/projectdiscovery/nuclei/v2/pkg/js/global" + "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/goconsole" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// Compiler provides a runtime to execute goja runtime +// based javascript scripts efficiently while also +// providing them access to custom modules defined in libs/. +type Compiler struct { + registry *require.Registry +} + +// New creates a new compiler for the goja runtime. +func New() *Compiler { + registry := new(require.Registry) // this can be shared by multiple runtimes + // autoregister console node module with default printer it uses gologger backend + require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter())) + return &Compiler{registry: registry} +} + +// ExecuteOptions provides options for executing a script. +type ExecuteOptions struct { + // Pool specifies whether to use a pool of goja runtimes + // Can be used to speedup execution but requires + // the script to not make any global changes. + Pool bool + + // CaptureOutput specifies whether to capture the output + // of the script execution. + CaptureOutput bool + + // CaptureVariables specifies the variables to capture + // from the script execution. + CaptureVariables []string + + // Callback can be used to register new runtime helper functions + // ex: export etc + Callback func(runtime *goja.Runtime) error +} + +// ExecuteArgs is the arguments to pass to the script. +type ExecuteArgs struct { + Args map[string]interface{} //these are protocol variables + TemplateCtx map[string]interface{} // templateCtx contains template scoped variables +} + +// NewExecuteArgs returns a new execute arguments. +func NewExecuteArgs() *ExecuteArgs { + return &ExecuteArgs{ + Args: make(map[string]interface{}), + TemplateCtx: make(map[string]interface{}), + } +} + +// ExecuteResult is the result of executing a script. +type ExecuteResult map[string]interface{} + +func NewExecuteResult() ExecuteResult { + return make(map[string]interface{}) +} + +// GetSuccess returns whether the script was successful or not. +func (e ExecuteResult) GetSuccess() bool { + val, ok := e["success"].(bool) + if !ok { + return false + } + return val +} + +// Execute executes a script with the default options. +func (c *Compiler) Execute(code string, args *ExecuteArgs) (ExecuteResult, error) { + return c.ExecuteWithOptions(code, args, &ExecuteOptions{}) +} + +// VM returns a new goja runtime for the compiler. +func (c *Compiler) VM() *goja.Runtime { + runtime := c.newRuntime(false) + runtime.SetParserOptions(parser.WithDisableSourceMaps) + c.registerHelpersForVM(runtime) + return runtime +} + +// ExecuteWithOptions executes a script with the provided options. +func (c *Compiler) ExecuteWithOptions(code string, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) { + defer func() { + if err := recover(); err != nil { + gologger.Error().Msgf("Recovered panic %s %v: %v", code, args, err) + gologger.Verbose().Msgf("%s", debug.Stack()) + return + } + }() + if opts == nil { + opts = &ExecuteOptions{} + } + runtime := c.newRuntime(opts.Pool) + c.registerHelpersForVM(runtime) + + // register runtime functions if any + if opts.Callback != nil { + if err := opts.Callback(runtime); err != nil { + return nil, err + } + } + + if args == nil { + args = NewExecuteArgs() + } + for k, v := range args.Args { + _ = runtime.Set(k, v) + } + if args.TemplateCtx == nil { + args.TemplateCtx = make(map[string]interface{}) + } + // merge all args into templatectx + args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args) + _ = runtime.Set("template", args.TemplateCtx) + + results, err := runtime.RunString(code) + if err != nil { + return nil, err + } + captured := results.Export() + + if opts.CaptureOutput { + return convertOutputToResult(captured) + } + if len(opts.CaptureVariables) > 0 { + return c.captureVariables(runtime, opts.CaptureVariables) + } + // success is true by default . since js throws errors on failure + // hence output result is always success + return ExecuteResult{"response": captured, "success": results.ToBoolean()}, nil +} + +// captureVariables captures the variables from the runtime. +func (c *Compiler) captureVariables(runtime *goja.Runtime, variables []string) (ExecuteResult, error) { + results := make(ExecuteResult, len(variables)) + for _, variable := range variables { + value := runtime.Get(variable) + if value == nil { + continue + } + results[variable] = value.Export() + } + return results, nil +} + +func convertOutputToResult(output interface{}) (ExecuteResult, error) { + marshalled, err := jsoniter.Marshal(output) + if err != nil { + return nil, errors.Wrap(err, "could not marshal output") + } + + var outputMap map[string]interface{} + if err := jsoniter.Unmarshal(marshalled, &outputMap); err != nil { + var v interface{} + if unmarshalErr := jsoniter.Unmarshal(marshalled, &v); unmarshalErr != nil { + return nil, unmarshalErr + } + outputMap = map[string]interface{}{"output": v} + return outputMap, nil + } + return outputMap, nil +} + +// newRuntime creates a new goja runtime +// TODO: Add support for runtime reuse for helper functions +func (c *Compiler) newRuntime(reuse bool) *goja.Runtime { + return protocolstate.NewJSRuntime() +} + +// registerHelpersForVM registers all the helper functions for the goja runtime. +func (c *Compiler) registerHelpersForVM(runtime *goja.Runtime) { + _ = c.registry.Enable(runtime) + // by default import below modules every time + _ = runtime.Set("console", require.Require(runtime, console.ModuleName)) + + // Register embedded scripts + if err := global.RegisterNativeScripts(runtime); err != nil { + gologger.Error().Msgf("Could not register scripts: %s\n", err) + } +} diff --git a/v2/pkg/js/compiler/compiler_test.go b/v2/pkg/js/compiler/compiler_test.go new file mode 100644 index 00000000..8a85a75e --- /dev/null +++ b/v2/pkg/js/compiler/compiler_test.go @@ -0,0 +1,79 @@ +package compiler + +import ( + "strings" + "testing" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" +) + +func TestNewCompilerConsoleDebug(t *testing.T) { + gotString := "" + gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) + gologger.DefaultLogger.SetWriter(&noopWriter{ + Callback: func(data []byte, level levels.Level) { + gotString = string(data) + }, + }) + + compiler := New() + _, err := compiler.Execute("console.log('hello world');", NewExecuteArgs()) + if err != nil { + t.Fatal(err) + } + if !strings.HasSuffix(gotString, "hello world") { + t.Fatalf("console.log not working, got=%v", gotString) + } +} + +func TestExecuteResultGetSuccess(t *testing.T) { + compiler := New() + result, err := compiler.Execute("1+1 == 2", NewExecuteArgs()) + if err != nil { + t.Fatal(err) + } + if result.GetSuccess() != true { + t.Fatalf("expected true, got=%v", result.GetSuccess()) + } +} + +func TestCompilerCaptureVariables(t *testing.T) { + compiler := New() + result, err := compiler.ExecuteWithOptions("var a = 1;", NewExecuteArgs(), &ExecuteOptions{CaptureVariables: []string{"a"}}) + if err != nil { + t.Fatal(err) + } + gotValue, ok := result["a"] + if !ok { + t.Fatalf("expected a to be present in the result") + } + if gotValue.(int64) != 1 { + t.Fatalf("expected a to be 1, got=%v", gotValue) + } +} + +func TestCompilerCaptureOutput(t *testing.T) { + compiler := New() + result, err := compiler.ExecuteWithOptions("let obj = {'a':'b'}; obj", NewExecuteArgs(), &ExecuteOptions{CaptureOutput: true}) + if err != nil { + t.Fatal(err) + } + gotValue, ok := result["a"] + if !ok { + t.Fatalf("expected a to be present in the result") + } + if gotValue.(string) != "b" { + t.Fatalf("expected a to be b, got=%v", gotValue) + } +} + +type noopWriter struct { + Callback func(data []byte, level levels.Level) +} + +func (n *noopWriter) Write(data []byte, level levels.Level) { + if n.Callback != nil { + n.Callback(data, level) + } +} diff --git a/v2/pkg/js/devtools/README.md b/v2/pkg/js/devtools/README.md new file mode 100644 index 00000000..373e4b92 --- /dev/null +++ b/v2/pkg/js/devtools/README.md @@ -0,0 +1,35 @@ +## devtools + +devtools contains tools and scripts to automate booring tasks related to javascript layer/ packages. + +### bindgen + +[bindgen](./bindgen/README.md) is a tool that automatically generated bindings for native go packages with 'goja' + +### jsdocgen + +[jsdocgen](./jsdocgen/README.md) is LLM (OpenAI) based dev tool it takes generated javascript files and annotes them with jsdoc comments using predefined prompt + +### scrapefuncs + +[scrapefuncs](./scrapefuncs/README.md) is a tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI) + + +### Generating API Reference (aka static site using javascript files using jsdoc) + +```console +jsdoc -R [Homepage.md] -r -d api_reference -t [optional: jsdoc theme to use] generated/js +``` + +generated static site will be available at `api_reference/` directory and can be verified using simplehttpserver + +```console +simplehttpserver +``` + +and then open `http://localhost:8000/` in browser + + +### Notes + +we currently use [clean-jsdoc-theme](https://www.npmjs.com/package/clean-jsdoc-theme) demo at [sample-jsproto-docs/](https://projectdiscovery.github.io/js-proto-docs/) \ No newline at end of file diff --git a/v2/pkg/js/devtools/bindgen/INSTALL.md b/v2/pkg/js/devtools/bindgen/INSTALL.md new file mode 100644 index 00000000..11cfb1e9 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/INSTALL.md @@ -0,0 +1,4 @@ +# INSTALL + +1. Requires `js-beautify` node plugin installed in `$PATH`. +2. Requires `gofmt` installed in `$PATH`. diff --git a/v2/pkg/js/devtools/bindgen/README.md b/v2/pkg/js/devtools/bindgen/README.md new file mode 100644 index 00000000..f4028d05 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/README.md @@ -0,0 +1,14 @@ +## bindgen (aka bindings generator) + +bindgen is a tool that automatically generated bindings for native go packages with 'goja' + +Native Go packages are available [here](../../libs/) + +Generated Output is available [here](../../generated/) + +bindgen generates 3 different types of outputs + +- `go` => this directory contains corresponding goja bindings (actual bindings code) ex: [kerberos.go](../../generated/go/libkerberos/kerberos.go) +- `js` => this is more of a javascript **representation** of all exposed functions and types etc in javascript ex: [kerberos.js](../../generated/js/libkerberos/kerberos.js) and does not server any functional purpose other than reference +- `markdown` => autogenerated markdown documentation for each library / package ex: [kerberos.md](../../generated/markdown/libkerberos/kerberos.md) + diff --git a/v2/pkg/js/devtools/bindgen/cmd/bindgen/main.go b/v2/pkg/js/devtools/bindgen/cmd/bindgen/main.go new file mode 100644 index 00000000..36c1f104 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/cmd/bindgen/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "flag" + "fmt" + "log" + "path" + "path/filepath" + + "github.com/pkg/errors" + generator "github.com/projectdiscovery/nuclei/v2/pkg/js/devtools/bindgen" + fileutil "github.com/projectdiscovery/utils/file" +) + +var ( + dir string + generatedDir string + targetModules string +) + +func main() { + flag.StringVar(&dir, "dir", "libs", "directory to process") + flag.StringVar(&generatedDir, "out", "generated", "directory to output generated files") + flag.StringVar(&targetModules, "target", "", "target modules to generate") + flag.Parse() + log.SetFlags(0) + if !fileutil.FolderExists(dir) { + log.Fatalf("directory %s does not exist", dir) + } + if err := process(); err != nil { + log.Fatal(err) + } +} + +func process() error { + modules, err := generator.GetLibraryModules(dir) + if err != nil { + return errors.Wrap(err, "could not get library modules") + } + if len(modules) == 0 && fileutil.FolderExists(dir) { + // if no modules are found, then given directory is the module itself + targetModules = path.Base(dir) + modules = append(modules, targetModules) + dir = filepath.Dir(dir) + } + for _, module := range modules { + log.Printf("[module] Generating %s", module) + + data, err := generator.CreateTemplateData(filepath.Join(dir, module), "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/") + if err != nil { + return fmt.Errorf("could not create template data: %v", err) + } + + prefixed := "lib" + module + err = data.WriteJSTemplate(filepath.Join(generatedDir, "js/"+prefixed), module) + if err != nil { + return fmt.Errorf("could not write js template: %v", err) + } + err = data.WriteGoTemplate(path.Join(generatedDir, "go/"+prefixed), module) + if err != nil { + return fmt.Errorf("could not write go template: %v", err) + } + // disabled for now since we have static website for docs + // err = data.WriteMarkdownLibraryDocumentation(path.Join(generatedDir, "markdown/"), module) + // if err != nil { + // return fmt.Errorf("could not write markdown template: %v", err) + // } + + // err = data.WriteMarkdownIndexTemplate(path.Join(generatedDir, "markdown/")) + // if err != nil { + // return fmt.Errorf("could not write markdown index template: %v", err) + // } + data.InitNativeScripts() + } + return nil +} diff --git a/v2/pkg/js/devtools/bindgen/generator.go b/v2/pkg/js/devtools/bindgen/generator.go new file mode 100644 index 00000000..6f46b2ee --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/generator.go @@ -0,0 +1,412 @@ +package generator + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "os" + "strings" + + _ "embed" + + "github.com/pkg/errors" + "github.com/projectdiscovery/nuclei/v2/pkg/js/compiler" +) + +var ( + //go:embed templates/js_class.tmpl + jsClassFile string + //go:embed templates/go_class.tmpl + goClassFile string + //go:embed templates/markdown_class.tmpl + markdownClassFile string +) + +// TemplateData contains the parameters for the JS code generator +type TemplateData struct { + PackageName string + PackagePath string + PackageFuncs map[string]string + PackageInterfaces map[string]string + PackageFuncsExtraNoType map[string]PackageFunctionExtra + PackageFuncsExtra map[string]PackageFuncExtra + PackageVars map[string]string + PackageVarsValues map[string]string + PackageTypes map[string]string + PackageTypesExtra map[string]PackageTypeExtra + + typesPackage *types.Package + + // NativeScripts contains the list of native scripts + // that should be included in the package. + NativeScripts []string +} + +// PackageTypeExtra contains extra information about a type +type PackageTypeExtra struct { + Fields map[string]string +} + +// PackageFuncExtra contains extra information about a function +type PackageFuncExtra struct { + Items map[string]PackageFunctionExtra + Doc string +} + +// PackageFunctionExtra contains extra information about a function +type PackageFunctionExtra struct { + Args []string + Name string + Returns []string + Doc string +} + +// newTemplateData creates a new template data structure +func newTemplateData(packagePrefix, pkgName string) *TemplateData { + return &TemplateData{ + PackageName: pkgName, + PackagePath: packagePrefix + pkgName, + PackageFuncs: make(map[string]string), + PackageFuncsExtraNoType: make(map[string]PackageFunctionExtra), + PackageFuncsExtra: make(map[string]PackageFuncExtra), + PackageVars: make(map[string]string), + PackageVarsValues: make(map[string]string), + PackageTypes: make(map[string]string), + PackageInterfaces: make(map[string]string), + PackageTypesExtra: make(map[string]PackageTypeExtra), + } +} + +// GetLibraryModules takes a directory and returns subdirectories as modules +func GetLibraryModules(directory string) ([]string, error) { + dirs, err := os.ReadDir(directory) + if err != nil { + return nil, errors.Wrap(err, "could not read directory") + } + var modules []string + for _, dir := range dirs { + if dir.IsDir() { + modules = append(modules, dir.Name()) + } + } + return modules, nil +} + +// CreateTemplateData creates a TemplateData structure from a directory +// of go source code. +func CreateTemplateData(directory string, packagePrefix string) (*TemplateData, error) { + fmt.Println(directory) + fset := token.NewFileSet() + + pkgs, err := parser.ParseDir(fset, directory, nil, parser.ParseComments) + if err != nil { + return nil, errors.Wrap(err, "could not parse directory") + } + if len(pkgs) != 1 { + return nil, fmt.Errorf("expected 1 package, got %d", len(pkgs)) + } + + config := &types.Config{ + Importer: importer.ForCompiler(fset, "source", nil), + } + var packageName string + var files []*ast.File + for k, v := range pkgs { + packageName = k + for _, f := range v.Files { + files = append(files, f) + } + break + } + + pkg, err := config.Check(packageName, fset, files, nil) + if err != nil { + return nil, errors.Wrap(err, "could not check package") + } + + var pkgMain *ast.Package + for _, p := range pkgs { + pkgMain = p + break + } + + log.Printf("[create] [discover] Package: %s\n", pkgMain.Name) + data := newTemplateData(packagePrefix, pkgMain.Name) + data.typesPackage = pkg + data.gatherPackageData(pkgMain, data) + + for item, v := range data.PackageFuncsExtra { + if len(v.Items) == 0 { + delete(data.PackageFuncsExtra, item) + } + } + return data, nil +} + +// InitNativeScripts initializes the native scripts array +// with all the exported functions from the runtime +func (d *TemplateData) InitNativeScripts() { + compiler := compiler.New() + runtime := compiler.VM() + + exports := runtime.Get("exports") + if exports == nil { + return + } + exportsObj := exports.Export() + if exportsObj == nil { + return + } + for v := range exportsObj.(map[string]interface{}) { + d.NativeScripts = append(d.NativeScripts, v) + } +} + +// gatherPackageData gathers data about the package +func (d *TemplateData) gatherPackageData(pkg *ast.Package, data *TemplateData) { + ast.Inspect(pkg, func(node ast.Node) bool { + switch node := node.(type) { + case *ast.FuncDecl: + extra := d.collectFuncDecl(node) + if extra.Name == "" { + return true + } + data.PackageFuncsExtraNoType[node.Name.Name] = extra + data.PackageFuncs[node.Name.Name] = node.Name.Name + case *ast.TypeSpec: + if !node.Name.IsExported() { + return true + } + if node.Type == nil { + return true + } + structDecl, ok := node.Type.(*ast.StructType) + if !ok { + return true + } + + packageTypes := PackageTypeExtra{ + Fields: make(map[string]string), + } + for _, field := range structDecl.Fields.List { + fieldName := field.Names[0].Name + + var fieldTypeValue string + switch fieldType := field.Type.(type) { + case *ast.Ident: // Field type is a simple identifier + fieldTypeValue = fieldType.Name + case *ast.ArrayType: + switch fieldType.Elt.(type) { + case *ast.Ident: + fieldTypeValue = fmt.Sprintf("[]%s", fieldType.Elt.(*ast.Ident).Name) + case *ast.StarExpr: + fieldTypeValue = fmt.Sprintf("[]%s", d.handleStarExpr(fieldType.Elt.(*ast.StarExpr))) + } + case *ast.SelectorExpr: // Field type is a qualified identifier + fieldTypeValue = fmt.Sprintf("%s.%s", fieldType.X, fieldType.Sel) + } + packageTypes.Fields[fieldName] = fieldTypeValue + } + if len(packageTypes.Fields) == 0 { + return true + } + data.PackageTypesExtra[node.Name.Name] = packageTypes + case *ast.GenDecl: + identifyGenDecl(pkg, node, data) + } + return true + }) +} + +func identifyGenDecl(pkg *ast.Package, decl *ast.GenDecl, data *TemplateData) { + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + if !spec.Names[0].IsExported() { + continue + } + if spec.Values == nil || len(spec.Values) == 0 { + continue + } + data.PackageVars[spec.Names[0].Name] = spec.Names[0].Name + data.PackageVarsValues[spec.Names[0].Name] = spec.Values[0].(*ast.BasicLit).Value + case *ast.TypeSpec: + if !spec.Name.IsExported() { + continue + } + if spec.Type == nil { + continue + } + + switch spec.Type.(type) { + case *ast.InterfaceType: + data.PackageInterfaces[spec.Name.Name] = convertCommentsToJavascript(decl.Doc.Text()) + + case *ast.StructType: + data.PackageFuncsExtra[spec.Name.Name] = PackageFuncExtra{ + Items: make(map[string]PackageFunctionExtra), + Doc: convertCommentsToJavascript(decl.Doc.Text()), + } + + // Traverse the AST. + collectStructFuncsFromAST(pkg, spec, data) + data.PackageTypes[spec.Name.Name] = spec.Name.Name + } + } + } +} + +func collectStructFuncsFromAST(pkg *ast.Package, spec *ast.TypeSpec, data *TemplateData) { + ast.Inspect(pkg, func(n ast.Node) bool { + if fn, isFunc := n.(*ast.FuncDecl); isFunc && fn.Name.IsExported() { + processFunc(fn, spec, data) + } + return true + }) +} + +func processFunc(fn *ast.FuncDecl, spec *ast.TypeSpec, data *TemplateData) { + if fn.Recv == nil || len(fn.Recv.List) == 0 { + return + } + + if t, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok { + if ident, ok := t.X.(*ast.Ident); ok && spec.Name.Name == ident.Name { + processFunctionDetails(fn, ident, data) + } + } +} + +func processFunctionDetails(fn *ast.FuncDecl, ident *ast.Ident, data *TemplateData) { + extra := PackageFunctionExtra{ + Name: fn.Name.Name, + Args: extractArgs(fn), + Doc: convertCommentsToJavascript(fn.Doc.Text()), + Returns: data.extractReturns(fn), + } + data.PackageFuncsExtra[ident.Name].Items[fn.Name.Name] = extra +} + +func extractArgs(fn *ast.FuncDecl) []string { + args := make([]string, 0) + for _, arg := range fn.Type.Params.List { + for _, name := range arg.Names { + args = append(args, name.Name) + } + } + return args +} + +func (d *TemplateData) extractReturns(fn *ast.FuncDecl) []string { + returns := make([]string, 0) + if fn.Type.Results == nil { + return returns + } + for _, ret := range fn.Type.Results.List { + returnType := d.extractReturnType(ret) + if returnType != "" { + returns = append(returns, returnType) + } + } + return returns +} + +func (d *TemplateData) extractReturnType(ret *ast.Field) string { + switch v := ret.Type.(type) { + case *ast.ArrayType: + if v, ok := v.Elt.(*ast.Ident); ok { + return fmt.Sprintf("[]%s", v.Name) + } + if v, ok := v.Elt.(*ast.StarExpr); ok { + return fmt.Sprintf("[]%s", d.handleStarExpr(v)) + } + case *ast.Ident: + return v.Name + case *ast.StarExpr: + return d.handleStarExpr(v) + } + return "" +} + +func (d *TemplateData) handleStarExpr(v *ast.StarExpr) string { + switch vk := v.X.(type) { + case *ast.Ident: + return vk.Name + case *ast.SelectorExpr: + if vk.X != nil { + d.collectTypeFromExternal(d.typesPackage, vk.X.(*ast.Ident).Name, vk.Sel.Name) + } + return vk.Sel.Name + } + return "" +} + +func (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) { + extra := PackageTypeExtra{ + Fields: make(map[string]string), + } + + for _, importValue := range pkg.Imports() { + if importValue.Name() != pkgName { + continue + } + obj := importValue.Scope().Lookup(name) + if obj == nil || !obj.Exported() { + continue + } + typeName, ok := obj.(*types.TypeName) + if !ok { + continue + } + underlying, ok := typeName.Type().Underlying().(*types.Struct) + if !ok { + continue + } + for i := 0; i < underlying.NumFields(); i++ { + field := underlying.Field(i) + fieldType := field.Type().String() + + if val, ok := field.Type().Underlying().(*types.Pointer); ok { + fieldType = field.Name() + d.collectTypeFromExternal(pkg, pkgName, val.Elem().(*types.Named).Obj().Name()) + } + if _, ok := field.Type().Underlying().(*types.Struct); ok { + fieldType = field.Name() + d.collectTypeFromExternal(pkg, pkgName, field.Name()) + } + extra.Fields[field.Name()] = fieldType + } + if len(extra.Fields) > 0 { + d.PackageTypesExtra[name] = extra + } + } +} + +func (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctionExtra) { + if decl.Recv != nil { + return + } + if !decl.Name.IsExported() { + return + } + extra.Name = decl.Name.Name + extra.Doc = convertCommentsToJavascript(decl.Doc.Text()) + + for _, arg := range decl.Type.Params.List { + for _, name := range arg.Names { + extra.Args = append(extra.Args, name.Name) + } + } + extra.Returns = d.extractReturns(decl) + return extra +} + +// convertCommentsToJavascript converts comments to javascript comments. +func convertCommentsToJavascript(comments string) string { + suffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, "\n", "\n// "), "// "), "\n") + return fmt.Sprintf("// %s", suffix) +} diff --git a/v2/pkg/js/devtools/bindgen/output.go b/v2/pkg/js/devtools/bindgen/output.go new file mode 100644 index 00000000..990d1fa4 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/output.go @@ -0,0 +1,160 @@ +package generator + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path" + "strings" + "text/template" + + "github.com/pkg/errors" +) + +// markdownIndexes is a map of markdown modules to their filename index +// +// It is used to generate the index.md file for the documentation +var markdownIndexes = make(map[string]string) + +// WriteGoTemplate writes the go template to the output file +func (d *TemplateData) WriteGoTemplate(outputDirectory string, pkgName string) error { + _ = os.MkdirAll(outputDirectory, os.ModePerm) + + var err error + tmpl := template.New("go_class") + tmpl = tmpl.Funcs(templateFuncs()) + tmpl, err = tmpl.Parse(goClassFile) + if err != nil { + return errors.Wrap(err, "could not parse go class template") + } + + filename := path.Join(outputDirectory, fmt.Sprintf("%s.go", pkgName)) + output, err := os.Create(filename) + if err != nil { + return errors.Wrap(err, "could not create go class template") + } + + if err := tmpl.Execute(output, d); err != nil { + output.Close() + return errors.Wrap(err, "could not execute go class template") + } + output.Close() + + cmd := exec.Command("gofmt", "-w", filename) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return errors.Wrap(err, "could not format go class template") + } + return nil +} + +// WriteJSTemplate writes the js template to the output file +func (d *TemplateData) WriteJSTemplate(outputDirectory string, pkgName string) error { + _ = os.MkdirAll(outputDirectory, os.ModePerm) + + var err error + tmpl := template.New("js_class") + tmpl, err = tmpl.Parse(jsClassFile) + if err != nil { + return errors.Wrap(err, "could not parse js class template") + } + + filename := path.Join(outputDirectory, fmt.Sprintf("%s.js", pkgName)) + output, err := os.Create(filename) + if err != nil { + return errors.Wrap(err, "could not create js class template") + } + + if err := tmpl.Execute(output, d); err != nil { + output.Close() + return errors.Wrap(err, "could not execute js class template") + } + output.Close() + + cmd := exec.Command("js-beautify", "-r", filename) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +// WriteMarkdownIndexTemplate writes the markdown documentation to the output file +func (d *TemplateData) WriteMarkdownIndexTemplate(outputDirectory string) error { + _ = os.MkdirAll(outputDirectory, os.ModePerm) + + filename := path.Join(outputDirectory, "index.md") + output, err := os.Create(filename) + if err != nil { + return errors.Wrap(err, "could not create markdown index template") + } + defer output.Close() + + buffer := &bytes.Buffer{} + _, _ = buffer.WriteString("# Index\n\n") + for _, v := range markdownIndexes { + _, _ = buffer.WriteString(fmt.Sprintf("* %s\n", v)) + } + _, _ = buffer.WriteString("\n\n") + + _, _ = buffer.WriteString("# Scripts\n\n") + for _, v := range d.NativeScripts { + _, _ = buffer.WriteString(fmt.Sprintf("* `%s`\n", v)) + } + if _, err := output.Write(buffer.Bytes()); err != nil { + return errors.Wrap(err, "could not write markdown index template") + } + return nil +} + +// WriteMarkdownLibraryDocumentation writes the markdown documentation for a js library +// to the output file +func (d *TemplateData) WriteMarkdownLibraryDocumentation(outputDirectory string, pkgName string) error { + var err error + _ = os.MkdirAll(outputDirectory, os.ModePerm) + + tmpl := template.New("markdown_class") + tmpl = tmpl.Funcs(templateFuncs()) + tmpl, err = tmpl.Parse(markdownClassFile) + if err != nil { + return errors.Wrap(err, "could not parse markdown class template") + } + + filename := path.Join(outputDirectory, fmt.Sprintf("%s.md", pkgName)) + output, err := os.Create(filename) + if err != nil { + return errors.Wrap(err, "could not create markdown class template") + } + + markdownIndexes[pkgName] = fmt.Sprintf("[%s](%s.md)", pkgName, pkgName) + if err := tmpl.Execute(output, d); err != nil { + output.Close() + return err + } + output.Close() + + return nil +} + +// templateFuncs returns the template functions for the generator +func templateFuncs() map[string]interface{} { + return map[string]interface{}{ + "exist": func(v map[string]string, key string) bool { + _, exist := v[key] + return exist + }, + "toTitle": func(v string) string { + if len(v) == 0 { + return v + } + + return strings.ToUpper(string(v[0])) + v[1:] + }, + "uncomment": func(v string) string { + return strings.ReplaceAll(strings.ReplaceAll(v, "// ", " "), "\n", " ") + }, + } +} diff --git a/v2/pkg/js/devtools/bindgen/templates/go_class.tmpl b/v2/pkg/js/devtools/bindgen/templates/go_class.tmpl new file mode 100644 index 00000000..3432430a --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/templates/go_class.tmpl @@ -0,0 +1,48 @@ +package {{.PackageName}} + +{{$pkgName:=(printf "lib_%s" .PackageName) -}} + +import ( + {{$pkgName}} "{{.PackagePath}}" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/{{.PackageName}}") +) + +func init() { + module.Set( + gojs.Objects{ + {{- $pkgFuncs:=.PackageFuncs}} + // Functions + {{- range $objName, $objDefine := .PackageFuncs}} + "{{$objName}}": {{$pkgName}}.{{$objDefine}}, + {{- end}} + + // Var and consts + {{- range $objName, $objDefine := .PackageVars}} + "{{$objName}}": {{$pkgName}}.{{$objDefine}}, + {{- end}} + + // Types (value type) + {{- range $objName, $objDefine := .PackageTypes}} + "{{$objName}}": {{printf "func() %s.%s { return %s.%s{} }" $pkgName $objDefine $pkgName $objDefine}}, + {{- end}} + + // Types (pointer type) + {{range $objName, $objDefine := .PackageTypes}} + {{- $newObjName := printf "%s%s" "New" $objName -}} + {{- if not (exist $pkgFuncs $newObjName) -}} + "{{$newObjName}}": {{printf "func() *%s.%s { return &%s.%s{} }" $pkgName $objDefine $pkgName $objDefine}}, + {{end -}} + {{- end -}} + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} \ No newline at end of file diff --git a/v2/pkg/js/devtools/bindgen/templates/js_class.tmpl b/v2/pkg/js/devtools/bindgen/templates/js_class.tmpl new file mode 100644 index 00000000..ca466128 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/templates/js_class.tmpl @@ -0,0 +1,37 @@ +{{$packageName:=(printf "%s" .PackageName) -}} +/**@module {{$packageName}} */ + + + +{{- range $typeName, $methods := .PackageFuncsExtra }} + +{{ $methods.Doc }} +class {{$typeName}} { + + {{- range $methodName, $method := $methods.Items }} + {{$method.Doc}} + {{ $method.Name }}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) { + return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}}; + }; + {{- end }} +}; + +{{- end }} + + +{{- range $objName, $method := .PackageFuncsExtraNoType}} +{{$method.Doc}} +function {{$objName}}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) { + return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}}; + }; +{{- end}} + + +module.exports = { +{{- range $typeName, $methods := .PackageFuncsExtra }} + {{$typeName}}: {{$typeName}}, +{{- end }} +{{- range $objName, $method := .PackageFuncsExtraNoType}} + {{$objName}}: {{$objName}}, +{{- end}} +}; \ No newline at end of file diff --git a/v2/pkg/js/devtools/bindgen/templates/markdown_class.tmpl b/v2/pkg/js/devtools/bindgen/templates/markdown_class.tmpl new file mode 100644 index 00000000..506532e8 --- /dev/null +++ b/v2/pkg/js/devtools/bindgen/templates/markdown_class.tmpl @@ -0,0 +1,71 @@ +{{$packageName:=(printf "%s" .PackageName) -}} +## {{$packageName}} +--- + + +`{{$packageName}}` implements bindings for `{{.PackageName}}` protocol in javascript +to be used from nuclei scanner. + + +{{ if .PackageFuncsExtra }} +## Types + +{{- range $typeName, $methods := .PackageFuncsExtra }} + +### {{$typeName}} + +{{ uncomment $methods.Doc }} + +| Method | Description | Arguments | Returns | +|--------|-------------|-----------|---------| +{{- range $methodName, $method := $methods.Items }} +| `{{$methodName}}` | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} | +{{- end }} + +{{- end }} +{{- end }} + +{{ if .PackageFuncsExtraNoType }} +## Exported Functions + +| Name | Description | Arguments | Returns | +|--------|-------------|-----------|---------| +{{- range $objName, $method := .PackageFuncsExtraNoType}} +{{$objName}} | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} | +{{- end}} +{{- end}} + +{{ if .PackageTypesExtra }} +## Exported Types Fields + +{{- range $typeName, $methods := .PackageTypesExtra }} +### {{$typeName}} + +| Name | Type | +|--------|-------------| +{{- range $fieldName, $field := $methods.Fields }} +| {{$fieldName}} | `{{ $field }}` | +{{- end }} +{{- end }} +{{- end }} + +{{ if .PackageVarsValues }} + +## Exported Variables Values + +| Name | Value | +|--------|-------------| +{{- range $varName, $var := .PackageVarsValues }} +| {{$varName}} | `{{ $var }}` | +{{- end }} +{{- end}} + +{{ if .PackageInterfaces }} +## Exported Interfaces + +{{- range $typeName, $doc := .PackageInterfaces }} +### {{$typeName}} + +{{ uncomment $doc }} +{{- end }} +{{- end }} diff --git a/v2/pkg/js/devtools/jsdocgen/README.md b/v2/pkg/js/devtools/jsdocgen/README.md new file mode 100644 index 00000000..10bf8241 --- /dev/null +++ b/v2/pkg/js/devtools/jsdocgen/README.md @@ -0,0 +1,116 @@ +## jsdocgen + +jsdocgen is LLM (OpenAI) based dev tool it takes generated javascript files and annotes them with jsdoc comments using predefined prompt + +### Usage + +```bash + ./jsdocgen -h +Usage of ./jsdocgen: + -dir string + directory to process + -key string + openai api key + -keyfile string + openai api key file +``` + +### Example + +```bash +./jsdocgen -dir modules/generated/js/libmysql -keyfile ~/.openai/key +``` + + +### Example Conversion + +when `bindgen` is executed it generates basic javascript (which currently is incorrect) and looks like this but the idea is to generate bare minimum that LLM has idea what we are trying to do + +```javascript +/**@module rdp */ +// rdp implements bindings for rdp protocol in javascript +// to be used from nuclei scanner. + +// RDPClient is a client for rdp servers +class RDPClient { + // CheckRDPAuth checks if the given host and port are running rdp server + // with authentication and returns their metadata. + CheckRDPAuth(host, port) { + return CheckRDPAuthResponse, error; + }; + // IsRDP checks if the given host and port are running rdp server. + // + // If connection is successful, it returns true. + // If connection is unsuccessful, it returns false and error. + // + // The Name of the OS is also returned if the connection is successful. + IsRDP(host, port) { + return IsRDPResponse, error; + }; +}; + + +module.exports = { + RDPClient: RDPClient, +}; +``` + +And when `jsdocgen` is executed it generates the following output + +```javascript +/** + * @module rdp + * This module implements bindings for rdp protocol in javascript to be used from nuclei scanner. + */ + +/** + * @class + * @classdesc RDPClient is a client for rdp servers + */ +class RDPClient { + /** + * @method + * @name CheckRDPAuth + * @description checks if the given host and port are running rdp server with authentication and returns their metadata. + * @param {string} host - The host of the rdp server + * @param {number} port - The port of the rdp server + * @returns {CheckRDPAuthResponse} - The response from the rdp server + * @throws {error} If there is an error in the request + * @example + * let client = new RDPClient(); + * client.CheckRDPAuth("localhost", 3389); + */ + CheckRDPAuth(host, port) { + // implemented in go + }; + + /** + * @method + * @name IsRDP + * @description checks if the given host and port are running rdp server. + * If connection is successful, it returns true. + * If connection is unsuccessful, it throws an error. + * The Name of the OS is also returned if the connection is successful. + * @param {string} host - The host of the rdp server + * @param {number} port - The port of the rdp server + * @returns {IsRDPResponse} - The response from the rdp server + * @throws {error} If there is an error in the request + * @example + * let client = new RDPClient(); + * client.IsRDP("localhost", 3389); + */ + IsRDP(host, port) { + // implemented in go + }; +}; + +module.exports = { + RDPClient: RDPClient, +}; +``` + +Now we can see the output is much more readable and make sense. + +## Note: + +jsdocgen is not perfect and it is not supposed to be, it is intended to **almost** automate boooring stuff but will always require some manual intervention to make it perfect. \ No newline at end of file diff --git a/v2/pkg/js/devtools/jsdocgen/jsdocgen b/v2/pkg/js/devtools/jsdocgen/jsdocgen new file mode 100755 index 00000000..f0320860 Binary files /dev/null and b/v2/pkg/js/devtools/jsdocgen/jsdocgen differ diff --git a/v2/pkg/js/devtools/jsdocgen/main.go b/v2/pkg/js/devtools/jsdocgen/main.go new file mode 100644 index 00000000..124b1fd6 --- /dev/null +++ b/v2/pkg/js/devtools/jsdocgen/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + "path/filepath" + "strings" + + errorutil "github.com/projectdiscovery/utils/errors" + fileutil "github.com/projectdiscovery/utils/file" + openai "github.com/sashabaranov/go-openai" +) + +var ( + dir string + key string + keyfile string +) + +const sysPrompt = ` +you are helpful coding assistant responsible for generating javascript file with js annotations for nuclei from a 'corrupted' javascript file. +--- example input --- +/** @module mymodule */ + +class TestClass { + function Execute(path){ + return []string , error + } +} + +function ListTests(){ + return Testcases , error +} + +module.exports = { + TestClass: TestClass, + ListTests: ListTests, +} +--- end example --- +--- example output --- +/** @module mymodule */ + +/** + * @class + * @classdesc TestClass is a class used for testing purposes + */ +class TestClass { + /** + @method + @description Execute executes the test and returns the results + @param {string} path - The path to execute the test on. + @returns {string[]} - The results of the test in an array. + @throws {error} - The error encountered during test execution. + @example + let m = require('nuclei/mymodule'); + let c = m.TestClass(); + let results = c.Execute('/tmp'); + */ + function Execute(path){ + // implemented in go + }; +}; + +/** + * @typdef {object} Testcases + * @description Testcases is a object containing all the tests. + */ + const Testcases = {}; + + + +/** + * @function + * @description ListTests lists all the tests available + * @returns {Testcases} - The testcases object containing all the tests. + * @throws {error} - The error encountered during test listing. + * @example + * let m = require('nuclei/mymodule'); + * let tests = m.ListTests(); + */ +function ListTests(){ + // implemented in go +}; + +module.exports = { + TestClass: TestClass, + ListTests: ListTests, +} +--- end example --- +--- instructions --- +1. DONOT ADD ANY NEW Annotation (@) Other than those already mentioned in above example +2. All Function/Class/Method body should be empty with comment 'implemented in go' +3. ALL MODULE IMPORT PATHS SHOULD BE 'nuclei/' +4. ALWAYS replace '[]byte' with Uint8Array and treat as equivalent +5. IF AND ONLY IF a function returns unknown objects (ex: LDAPResponse etc) only then create a @typedef and its respecitve declaration using const = {} +6. DONOT create a typedef for built in and known types like string,int,float,[]byte,bool etc +7. JsDOC comments **must** always start with /** and end with */ and each line should start with * (star) +--- end instructions --- +` + +const userPrompt = ` +---original javascript--- +{{source}} +---new javascript--- +` + +// doclint is automatic javascript documentation linter for nuclei +// it uses LLM to autocomplete the generated js code to proper JSDOC notation +func main() { + flag.StringVar(&dir, "dir", "", "directory to process") + flag.StringVar(&key, "key", "", "openai api key") + flag.StringVar(&keyfile, "keyfile", "", "openai api key file") + flag.Parse() + log.SetFlags(0) + + if dir == "" { + log.Fatal("dir is not set") + } + finalKey := "" + if key != "" { + key = finalKey + } + if keyfile != "" && fileutil.FileExists(keyfile) { + data, err := os.ReadFile(keyfile) + if err != nil { + log.Fatal(err) + } + finalKey = string(data) + } + if key := os.Getenv("OPENAI_API_KEY"); key != "" { + finalKey = key + } + + if finalKey == "" { + log.Fatal("openai api key is not set") + } + llm := openai.NewClient(finalKey) + + _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + if !d.IsDir() && filepath.Ext(path) == ".js" { + log.Printf("Processing %s", path) + if err := updateDocsWithLLM(llm, path); err != nil { + log.Printf("Error processing %s: %s", path, err) + } else { + log.Printf("Processed %s", path) + } + } + return nil + }) +} + +// updateDocsWithLLM updates the documentation of a javascript file +func updateDocsWithLLM(llm *openai.Client, path string) error { + // read the file + bin, err := os.ReadFile(path) + if err != nil { + return err + } + + resp, err := llm.CreateChatCompletion(context.TODO(), openai.ChatCompletionRequest{ + Model: "gpt-4", + Messages: []openai.ChatCompletionMessage{ + {Role: "system", Content: sysPrompt}, + {Role: "user", Content: strings.ReplaceAll(userPrompt, "{{source}}", string(bin))}, + }, + Temperature: 0.1, + }) + if err != nil { + return err + } + if len(resp.Choices) == 0 { + return errorutil.New("no choices returned") + } + data := resp.Choices[0].Message.Content + return os.WriteFile(path, []byte(data), 0644) +} diff --git a/v2/pkg/js/devtools/scrapefuncs/README.md b/v2/pkg/js/devtools/scrapefuncs/README.md new file mode 100644 index 00000000..de7c29ce --- /dev/null +++ b/v2/pkg/js/devtools/scrapefuncs/README.md @@ -0,0 +1,130 @@ +## scrapefuncs + +scrapefuncs is go/ast based tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI) + +### Usage + +```console +Usage of ./scrapefuncs: + -dir string + directory to process (default "pkg/js/global") + -key string + openai api key + -keyfile string + openai api key file + -out string + output js file with declarations of all global functions +``` + + +### Example + +```console +$ ./scrapefuncs -keyfile ~/.openai.key +[+] Scraped 7 functions + +Name: Rand +Signatures: "Rand(n int) []byte" +Description: Rand returns a random byte slice of length n + +Name: RandInt +Signatures: "RandInt() int" +Description: RandInt returns a random int + +Name: log +Signatures: "log(msg string)" +Signatures: "log(msg map[string]interface{})" +Description: log prints given input to stdout with [JS] prefix for debugging purposes + +Name: getNetworkPort +Signatures: "getNetworkPort(port string, defaultPort string) string" +Description: getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols + +Name: isPortOpen +Signatures: "isPortOpen(host string, port string, [timeout int]) bool" +Description: isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds + +Name: ToBytes +Signatures: "ToBytes(...interface{}) []byte" +Description: ToBytes converts given input to byte slice + +Name: ToString +Signatures: "ToString(...interface{}) string" +Description: ToString converts given input to string + + +[+] Generating jsdoc for all functions + +/** + * Rand returns a random byte slice of length n + * Rand(n int) []byte + * @function + * @param {number} n - The length of the byte slice. + */ +function Rand(n) { + // implemented in go +}; + +/** + * RandInt returns a random int + * RandInt() int + * @function + */ +function RandInt() { + // implemented in go +}; + +/** + * log prints given input to stdout with [JS] prefix for debugging purposes + * log(msg string) + * log(msg map[string]interface{}) + * @function + * @param {string|Object} msg - The message to print. + */ +function log(msg) { + // implemented in go +}; + +/** + * getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols + * getNetworkPort(port string, defaultPort string) string + * @function + * @param {string} port - The port to check. + * @param {string} defaultPort - The default port to return if the port is colliding. + */ +function getNetworkPort(port, defaultPort) { + // implemented in go +}; + +/** + * isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds + * isPortOpen(host string, port string, [timeout int]) bool + * @function + * @param {string} host - The host to check. + * @param {string} port - The port to check. + * @param {number} [timeout=5] - The timeout in seconds. + */ +function isPortOpen(host, port, timeout = 5) { + // implemented in go +}; + +/** + * ToBytes converts given input to byte slice + * ToBytes(...interface{}) []byte + * @function + * @param {...any} args - The input to convert. + */ +function ToBytes(...args) { + // implemented in go +}; + +/** + * ToString converts given input to string + * ToString(...interface{}) string + * @function + * @param {...any} args - The input to convert. + */ +function ToString(...args) { + // implemented in go +}; +``` \ No newline at end of file diff --git a/v2/pkg/js/devtools/scrapefuncs/main.go b/v2/pkg/js/devtools/scrapefuncs/main.go new file mode 100644 index 00000000..04c62f91 --- /dev/null +++ b/v2/pkg/js/devtools/scrapefuncs/main.go @@ -0,0 +1,157 @@ +package main + +import ( + "bytes" + "context" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "strings" + + filutil "github.com/projectdiscovery/utils/file" + "github.com/sashabaranov/go-openai" +) + +var sysprompt = ` +data present after ---raw data--- contains raw data extracted by a parser and contains information about function +--- example --- +Name: log +Signatures: "log(msg string)" +Signatures: "log(msg map[string]interface{})" +Description: log prints given input to stdout with [JS] prefix for debugging purposes +--- end example --- +Here Name is name of function , signature[s] is actual function declaration and description is description of function +using this data for every such function generate a abstract implementation of function in javascript along with jsdoc annotations +--- example expected output--- +/** + * log prints given input to stdout with [JS] prefix for debugging purposes + * log(msg string) + * log(msg map[string]interface{}) + * @function + * @param {string} msg - The message to print. + */ +function log(msg) { + // implemented in go +}; +--- instructions --- +ACT as helpful coding assistant and do the same for all functions present in data +` + +const userPrompt = ` +---raw data--- +{{source}} +---new javascript--- +` + +var ( + dir string + key string + keyfile string + out string +) + +func main() { + flag.StringVar(&dir, "dir", "pkg/js/global", "directory to process") + flag.StringVar(&key, "key", "", "openai api key") + flag.StringVar(&keyfile, "keyfile", "", "openai api key file") + flag.StringVar(&out, "out", "", "output js file with declarations of all global functions") + flag.Parse() + + finalKey := "" + if key != "" { + key = finalKey + } + if keyfile != "" && filutil.FileExists(keyfile) { + data, err := os.ReadFile(keyfile) + if err != nil { + log.Fatal(err) + } + finalKey = string(data) + } + if key := os.Getenv("OPENAI_API_KEY"); key != "" { + finalKey = key + } + + if finalKey == "" { + log.Fatal("openai api key is not set") + } + llm := openai.NewClient(finalKey) + var buff bytes.Buffer + + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, dir, nil, 0) + if err != nil { + fmt.Println(err) + return + } + + for _, pkg := range pkgs { + for _, file := range pkg.Files { + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.CallExpr: + if sel, ok := x.Fun.(*ast.SelectorExpr); ok { + if sel.Sel.Name == "RegisterFuncWithSignature" { + for _, arg := range x.Args { + if kv, ok := arg.(*ast.CompositeLit); ok { + for _, elt := range kv.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + key := kv.Key.(*ast.Ident).Name + switch key { + case "Name", "Description": + buff.WriteString(fmt.Sprintf("%s: %s\n", key, strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`))) + case "Signatures": + if comp, ok := kv.Value.(*ast.CompositeLit); ok { + for _, signature := range comp.Elts { + buff.WriteString(fmt.Sprintf("%s: %s\n", key, signature.(*ast.BasicLit).Value)) + } + } + } + } + } + } + } + buff.WriteString("\n") + } + } + } + return true + }) + } + } + + fmt.Printf("[+] Scraped %d functions\n\n", strings.Count(buff.String(), "Name:")) + fmt.Println(buff.String()) + + fmt.Printf("[+] Generating jsdoc for all functions\n\n") + resp, err := llm.CreateChatCompletion(context.TODO(), openai.ChatCompletionRequest{ + Model: "gpt-4", + Messages: []openai.ChatCompletionMessage{ + {Role: "system", Content: sysprompt}, + {Role: "user", Content: strings.ReplaceAll(userPrompt, "{{source}}", buff.String())}, + }, + Temperature: 0.1, + }) + if err != nil { + fmt.Println(err) + return + } + if len(resp.Choices) == 0 { + fmt.Println("no choices returned") + return + } + data := resp.Choices[0].Message.Content + + fmt.Println(data) + + if out != "" { + if err := os.WriteFile(out, []byte(data), 0600); err != nil { + fmt.Println(err) + return + } + } +} diff --git a/v2/pkg/js/generated/README.md b/v2/pkg/js/generated/README.md new file mode 100644 index 00000000..7a995ec7 --- /dev/null +++ b/v2/pkg/js/generated/README.md @@ -0,0 +1,5 @@ +## generated + +!! Warning !! This is generated code, do not edit manually !! + +To make any changes to this code, please refer to [bindgen](../devtools/bindgen/README.md) \ No newline at end of file diff --git a/v2/pkg/js/generated/go/libbytes/bytes.go b/v2/pkg/js/generated/go/libbytes/bytes.go new file mode 100644 index 00000000..dbbe273c --- /dev/null +++ b/v2/pkg/js/generated/go/libbytes/bytes.go @@ -0,0 +1,32 @@ +package bytes + +import ( + lib_bytes "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/bytes" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/bytes") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "NewBuffer": lib_bytes.NewBuffer, + + // Var and consts + + // Types (value type) + "Buffer": func() lib_bytes.Buffer { return lib_bytes.Buffer{} }, + + // Types (pointer type) + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libfs/fs.go b/v2/pkg/js/generated/go/libfs/fs.go new file mode 100644 index 00000000..371b0da1 --- /dev/null +++ b/v2/pkg/js/generated/go/libfs/fs.go @@ -0,0 +1,34 @@ +package fs + +import ( + lib_fs "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/fs" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/fs") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "ListDir": lib_fs.ListDir, + "ReadFile": lib_fs.ReadFile, + "ReadFileAsString": lib_fs.ReadFileAsString, + "ReadFilesFromDir": lib_fs.ReadFilesFromDir, + + // Var and consts + + // Types (value type) + + // Types (pointer type) + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libgoconsole/goconsole.go b/v2/pkg/js/generated/go/libgoconsole/goconsole.go new file mode 100644 index 00000000..9885471f --- /dev/null +++ b/v2/pkg/js/generated/go/libgoconsole/goconsole.go @@ -0,0 +1,32 @@ +package goconsole + +import ( + lib_goconsole "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/goconsole" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/goconsole") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "NewGoConsolePrinter": lib_goconsole.NewGoConsolePrinter, + + // Var and consts + + // Types (value type) + "GoConsolePrinter": func() lib_goconsole.GoConsolePrinter { return lib_goconsole.GoConsolePrinter{} }, + + // Types (pointer type) + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libikev2/ikev2.go b/v2/pkg/js/generated/go/libikev2/ikev2.go new file mode 100644 index 00000000..c99c27d2 --- /dev/null +++ b/v2/pkg/js/generated/go/libikev2/ikev2.go @@ -0,0 +1,44 @@ +package ikev2 + +import ( + lib_ikev2 "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/ikev2" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/ikev2") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + "IKE_EXCHANGE_AUTH": lib_ikev2.IKE_EXCHANGE_AUTH, + "IKE_EXCHANGE_CREATE_CHILD_SA": lib_ikev2.IKE_EXCHANGE_CREATE_CHILD_SA, + "IKE_EXCHANGE_INFORMATIONAL": lib_ikev2.IKE_EXCHANGE_INFORMATIONAL, + "IKE_EXCHANGE_SA_INIT": lib_ikev2.IKE_EXCHANGE_SA_INIT, + "IKE_FLAGS_InitiatorBitCheck": lib_ikev2.IKE_FLAGS_InitiatorBitCheck, + "IKE_NOTIFY_NO_PROPOSAL_CHOSEN": lib_ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN, + "IKE_NOTIFY_USE_TRANSPORT_MODE": lib_ikev2.IKE_NOTIFY_USE_TRANSPORT_MODE, + "IKE_VERSION_2": lib_ikev2.IKE_VERSION_2, + + // Types (value type) + "IKEMessage": func() lib_ikev2.IKEMessage { return lib_ikev2.IKEMessage{} }, + "IKENonce": func() lib_ikev2.IKENonce { return lib_ikev2.IKENonce{} }, + "IKENotification": func() lib_ikev2.IKENotification { return lib_ikev2.IKENotification{} }, + + // Types (pointer type) + "NewIKEMessage": func() *lib_ikev2.IKEMessage { return &lib_ikev2.IKEMessage{} }, + "NewIKENonce": func() *lib_ikev2.IKENonce { return &lib_ikev2.IKENonce{} }, + "NewIKENotification": func() *lib_ikev2.IKENotification { return &lib_ikev2.IKENotification{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libkerberos/kerberos.go b/v2/pkg/js/generated/go/libkerberos/kerberos.go new file mode 100644 index 00000000..d38cf1ec --- /dev/null +++ b/v2/pkg/js/generated/go/libkerberos/kerberos.go @@ -0,0 +1,34 @@ +package kerberos + +import ( + lib_kerberos "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/kerberos" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/kerberos") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "EnumerateUserResponse": func() lib_kerberos.EnumerateUserResponse { return lib_kerberos.EnumerateUserResponse{} }, + "KerberosClient": func() lib_kerberos.KerberosClient { return lib_kerberos.KerberosClient{} }, + + // Types (pointer type) + "NewEnumerateUserResponse": func() *lib_kerberos.EnumerateUserResponse { return &lib_kerberos.EnumerateUserResponse{} }, + "NewKerberosClient": func() *lib_kerberos.KerberosClient { return &lib_kerberos.KerberosClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libldap/ldap.go b/v2/pkg/js/generated/go/libldap/ldap.go new file mode 100644 index 00000000..3ed1245f --- /dev/null +++ b/v2/pkg/js/generated/go/libldap/ldap.go @@ -0,0 +1,34 @@ +package ldap + +import ( + lib_ldap "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/ldap" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/ldap") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "LDAPMetadata": func() lib_ldap.LDAPMetadata { return lib_ldap.LDAPMetadata{} }, + "LdapClient": func() lib_ldap.LdapClient { return lib_ldap.LdapClient{} }, + + // Types (pointer type) + "NewLDAPMetadata": func() *lib_ldap.LDAPMetadata { return &lib_ldap.LDAPMetadata{} }, + "NewLdapClient": func() *lib_ldap.LdapClient { return &lib_ldap.LdapClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libmssql/mssql.go b/v2/pkg/js/generated/go/libmssql/mssql.go new file mode 100644 index 00000000..884dc0ee --- /dev/null +++ b/v2/pkg/js/generated/go/libmssql/mssql.go @@ -0,0 +1,32 @@ +package mssql + +import ( + lib_mssql "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/mssql" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/mssql") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "MSSQLClient": func() lib_mssql.MSSQLClient { return lib_mssql.MSSQLClient{} }, + + // Types (pointer type) + "NewMSSQLClient": func() *lib_mssql.MSSQLClient { return &lib_mssql.MSSQLClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libmysql/mysql.go b/v2/pkg/js/generated/go/libmysql/mysql.go new file mode 100644 index 00000000..6a570456 --- /dev/null +++ b/v2/pkg/js/generated/go/libmysql/mysql.go @@ -0,0 +1,32 @@ +package mysql + +import ( + lib_mysql "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/mysql" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/mysql") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "MySQLClient": func() lib_mysql.MySQLClient { return lib_mysql.MySQLClient{} }, + + // Types (pointer type) + "NewMySQLClient": func() *lib_mysql.MySQLClient { return &lib_mysql.MySQLClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libnet/net.go b/v2/pkg/js/generated/go/libnet/net.go new file mode 100644 index 00000000..22338ca2 --- /dev/null +++ b/v2/pkg/js/generated/go/libnet/net.go @@ -0,0 +1,34 @@ +package net + +import ( + lib_net "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/net" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/net") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "Open": lib_net.Open, + "OpenTLS": lib_net.OpenTLS, + + // Var and consts + + // Types (value type) + "NetConn": func() lib_net.NetConn { return lib_net.NetConn{} }, + + // Types (pointer type) + "NewNetConn": func() *lib_net.NetConn { return &lib_net.NetConn{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/liboracle/oracle.go b/v2/pkg/js/generated/go/liboracle/oracle.go new file mode 100644 index 00000000..d72d8bbd --- /dev/null +++ b/v2/pkg/js/generated/go/liboracle/oracle.go @@ -0,0 +1,34 @@ +package oracle + +import ( + lib_oracle "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/oracle" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/oracle") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsOracleResponse": func() lib_oracle.IsOracleResponse { return lib_oracle.IsOracleResponse{} }, + "OracleClient": func() lib_oracle.OracleClient { return lib_oracle.OracleClient{} }, + + // Types (pointer type) + "NewIsOracleResponse": func() *lib_oracle.IsOracleResponse { return &lib_oracle.IsOracleResponse{} }, + "NewOracleClient": func() *lib_oracle.OracleClient { return &lib_oracle.OracleClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libpop3/pop3.go b/v2/pkg/js/generated/go/libpop3/pop3.go new file mode 100644 index 00000000..273e0a20 --- /dev/null +++ b/v2/pkg/js/generated/go/libpop3/pop3.go @@ -0,0 +1,34 @@ +package pop3 + +import ( + lib_pop3 "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/pop3" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/pop3") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsPOP3Response": func() lib_pop3.IsPOP3Response { return lib_pop3.IsPOP3Response{} }, + "Pop3Client": func() lib_pop3.Pop3Client { return lib_pop3.Pop3Client{} }, + + // Types (pointer type) + "NewIsPOP3Response": func() *lib_pop3.IsPOP3Response { return &lib_pop3.IsPOP3Response{} }, + "NewPop3Client": func() *lib_pop3.Pop3Client { return &lib_pop3.Pop3Client{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libpostgres/postgres.go b/v2/pkg/js/generated/go/libpostgres/postgres.go new file mode 100644 index 00000000..9dca20d9 --- /dev/null +++ b/v2/pkg/js/generated/go/libpostgres/postgres.go @@ -0,0 +1,32 @@ +package postgres + +import ( + lib_postgres "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/postgres" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/postgres") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "PGClient": func() lib_postgres.PGClient { return lib_postgres.PGClient{} }, + + // Types (pointer type) + "NewPGClient": func() *lib_postgres.PGClient { return &lib_postgres.PGClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/librdp/rdp.go b/v2/pkg/js/generated/go/librdp/rdp.go new file mode 100644 index 00000000..7212a08a --- /dev/null +++ b/v2/pkg/js/generated/go/librdp/rdp.go @@ -0,0 +1,36 @@ +package rdp + +import ( + lib_rdp "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/rdp" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/rdp") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "CheckRDPAuthResponse": func() lib_rdp.CheckRDPAuthResponse { return lib_rdp.CheckRDPAuthResponse{} }, + "IsRDPResponse": func() lib_rdp.IsRDPResponse { return lib_rdp.IsRDPResponse{} }, + "RDPClient": func() lib_rdp.RDPClient { return lib_rdp.RDPClient{} }, + + // Types (pointer type) + "NewCheckRDPAuthResponse": func() *lib_rdp.CheckRDPAuthResponse { return &lib_rdp.CheckRDPAuthResponse{} }, + "NewIsRDPResponse": func() *lib_rdp.IsRDPResponse { return &lib_rdp.IsRDPResponse{} }, + "NewRDPClient": func() *lib_rdp.RDPClient { return &lib_rdp.RDPClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libredis/redis.go b/v2/pkg/js/generated/go/libredis/redis.go new file mode 100644 index 00000000..e52a1f01 --- /dev/null +++ b/v2/pkg/js/generated/go/libredis/redis.go @@ -0,0 +1,35 @@ +package redis + +import ( + lib_redis "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/redis" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/redis") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "Connect": lib_redis.Connect, + "GetServerInfo": lib_redis.GetServerInfo, + "GetServerInfoAuth": lib_redis.GetServerInfoAuth, + "IsAuthenticated": lib_redis.IsAuthenticated, + "RunLuaScript": lib_redis.RunLuaScript, + + // Var and consts + + // Types (value type) + + // Types (pointer type) + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/librsync/rsync.go b/v2/pkg/js/generated/go/librsync/rsync.go new file mode 100644 index 00000000..eca6a37c --- /dev/null +++ b/v2/pkg/js/generated/go/librsync/rsync.go @@ -0,0 +1,34 @@ +package rsync + +import ( + lib_rsync "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/rsync" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/rsync") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsRsyncResponse": func() lib_rsync.IsRsyncResponse { return lib_rsync.IsRsyncResponse{} }, + "RsyncClient": func() lib_rsync.RsyncClient { return lib_rsync.RsyncClient{} }, + + // Types (pointer type) + "NewIsRsyncResponse": func() *lib_rsync.IsRsyncResponse { return &lib_rsync.IsRsyncResponse{} }, + "NewRsyncClient": func() *lib_rsync.RsyncClient { return &lib_rsync.RsyncClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libsmb/smb.go b/v2/pkg/js/generated/go/libsmb/smb.go new file mode 100644 index 00000000..dc92186c --- /dev/null +++ b/v2/pkg/js/generated/go/libsmb/smb.go @@ -0,0 +1,32 @@ +package smb + +import ( + lib_smb "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/smb" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/smb") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "SMBClient": func() lib_smb.SMBClient { return lib_smb.SMBClient{} }, + + // Types (pointer type) + "NewSMBClient": func() *lib_smb.SMBClient { return &lib_smb.SMBClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libsmtp/smtp.go b/v2/pkg/js/generated/go/libsmtp/smtp.go new file mode 100644 index 00000000..aad05e4b --- /dev/null +++ b/v2/pkg/js/generated/go/libsmtp/smtp.go @@ -0,0 +1,34 @@ +package smtp + +import ( + lib_smtp "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/smtp" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/smtp") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsSMTPResponse": func() lib_smtp.IsSMTPResponse { return lib_smtp.IsSMTPResponse{} }, + "SMTPClient": func() lib_smtp.SMTPClient { return lib_smtp.SMTPClient{} }, + + // Types (pointer type) + "NewIsSMTPResponse": func() *lib_smtp.IsSMTPResponse { return &lib_smtp.IsSMTPResponse{} }, + "NewSMTPClient": func() *lib_smtp.SMTPClient { return &lib_smtp.SMTPClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libssh/ssh.go b/v2/pkg/js/generated/go/libssh/ssh.go new file mode 100644 index 00000000..f70fc78f --- /dev/null +++ b/v2/pkg/js/generated/go/libssh/ssh.go @@ -0,0 +1,32 @@ +package ssh + +import ( + lib_ssh "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/ssh" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/ssh") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "SSHClient": func() lib_ssh.SSHClient { return lib_ssh.SSHClient{} }, + + // Types (pointer type) + "NewSSHClient": func() *lib_ssh.SSHClient { return &lib_ssh.SSHClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libstructs/structs.go b/v2/pkg/js/generated/go/libstructs/structs.go new file mode 100644 index 00000000..eddbb3cd --- /dev/null +++ b/v2/pkg/js/generated/go/libstructs/structs.go @@ -0,0 +1,33 @@ +package structs + +import ( + lib_structs "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/structs" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/structs") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + "Pack": lib_structs.Pack, + "StructsCalcSize": lib_structs.StructsCalcSize, + "Unpack": lib_structs.Unpack, + + // Var and consts + + // Types (value type) + + // Types (pointer type) + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libtelnet/telnet.go b/v2/pkg/js/generated/go/libtelnet/telnet.go new file mode 100644 index 00000000..9d75977a --- /dev/null +++ b/v2/pkg/js/generated/go/libtelnet/telnet.go @@ -0,0 +1,34 @@ +package telnet + +import ( + lib_telnet "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/telnet" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/telnet") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsTelnetResponse": func() lib_telnet.IsTelnetResponse { return lib_telnet.IsTelnetResponse{} }, + "TelnetClient": func() lib_telnet.TelnetClient { return lib_telnet.TelnetClient{} }, + + // Types (pointer type) + "NewIsTelnetResponse": func() *lib_telnet.IsTelnetResponse { return &lib_telnet.IsTelnetResponse{} }, + "NewTelnetClient": func() *lib_telnet.TelnetClient { return &lib_telnet.TelnetClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/go/libvnc/vnc.go b/v2/pkg/js/generated/go/libvnc/vnc.go new file mode 100644 index 00000000..a5e2bc85 --- /dev/null +++ b/v2/pkg/js/generated/go/libvnc/vnc.go @@ -0,0 +1,34 @@ +package vnc + +import ( + lib_vnc "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/vnc" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" +) + +var ( + module = gojs.NewGojaModule("nuclei/vnc") +) + +func init() { + module.Set( + gojs.Objects{ + // Functions + + // Var and consts + + // Types (value type) + "IsVNCResponse": func() lib_vnc.IsVNCResponse { return lib_vnc.IsVNCResponse{} }, + "VNCClient": func() lib_vnc.VNCClient { return lib_vnc.VNCClient{} }, + + // Types (pointer type) + "NewIsVNCResponse": func() *lib_vnc.IsVNCResponse { return &lib_vnc.IsVNCResponse{} }, + "NewVNCClient": func() *lib_vnc.VNCClient { return &lib_vnc.VNCClient{} }, + }, + ).Register() +} + +func Enable(runtime *goja.Runtime) { + module.Enable(runtime) +} diff --git a/v2/pkg/js/generated/js/global.js b/v2/pkg/js/generated/js/global.js new file mode 100644 index 00000000..bcbdfd7a --- /dev/null +++ b/v2/pkg/js/generated/js/global.js @@ -0,0 +1,86 @@ +/** + * @function + * @description Rand returns a random byte slice of length n + * @param {number} n - The length of the byte slice. + * @returns {Uint8Array} - The random byte slice. + * @example + * let randbytes = Rand(10); // returns a random byte slice of length 10 + */ +function Rand(n) { + // implemented in go +}; + +/** + * @function + * @description RandInt returns a random int + * @returns {number} - The random integer. + * @example + * let myint = m.RandInt(); // returns a random int + */ +function RandInt() { + // implemented in go +}; + +/** + * @function + * @description log prints given input to stdout with [JS] prefix for debugging purposes + * @param {string|Object} msg - The message to print. + * @example + * log("Hello World!"); + * log({"Hello": "World!"}); + */ +function log(msg) { + // implemented in go +}; + +/** + * @function + * @description getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols + * @param {string} port - The port to check. + * @param {string} defaultPort - The default port to return if the given port is colliding. + * @returns {string} - The default port if the given port is colliding, otherwise the given port. + * @example + * let port = getNetworkPort(Port, "2843"); // 2843 is default port (even if 80,443 etc is given in Port from input) + */ +function getNetworkPort(port, defaultPort) { + // implemented in go +}; + +/** + * @function + * @description isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds + * @param {string} host - The host to check. + * @param {string} port - The port to check. + * @param {number} [timeout=5] - The timeout in seconds. + * @returns {boolean} - True if the port is open, false otherwise. + * @example + * let open = isPortOpen("localhost", "80"); // returns true if port 80 is open on localhost + * let open = isPortOpen("localhost", "80", 10); // returns true if port 80 is open on localhost within 10 seconds + */ +function isPortOpen(host, port, timeout = 5) { + // implemented in go +}; + +/** + * @function + * @description ToBytes converts given input to byte slice + * @param {...any} args - The input to convert. + * @returns {Uint8Array} - The byte slice. + * @example + * let mybytes = ToBytes("Hello World!"); // returns byte slice of "Hello World!" + */ +function ToBytes(...args) { + // implemented in go +}; + +/** + * @function + * @description ToString converts given input to string + * @param {...any} args - The input to convert. + * @returns {string} - The string. + * @example + * let mystr = ToString([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // returns "Hello" + */ +function ToString(...args) { + // implemented in go +}; diff --git a/v2/pkg/js/generated/js/libbytes/bytes.js b/v2/pkg/js/generated/js/libbytes/bytes.js new file mode 100644 index 00000000..bd0b1f4f --- /dev/null +++ b/v2/pkg/js/generated/js/libbytes/bytes.js @@ -0,0 +1,134 @@ +/**@module bytes */ + +/** + * @class + * @classdesc Buffer is a minimal buffer implementation to store and retrieve data + */ +class Buffer { + /** + * @method + * @description Bytes returns the byte slice of the buffer. + * @returns {Uint8Array} - The byte slice of the buffer. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * let bytes = b.Bytes(); + */ + Bytes() { + // implemented in go + }; + + /** + * @method + * @description Hex returns the hex representation of the buffer. + * @returns {string} - The hex representation of the buffer. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * let hex = b.Hex(); + */ + Hex() { + // implemented in go + }; + + /** + * @method + * @description Hexdump returns the hexdump representation of the buffer. + * @returns {string} - The hexdump representation of the buffer. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * let hexdump = b.Hexdump(); + */ + Hexdump() { + // implemented in go + }; + + /** + * @method + * @description Len returns the length of the buffer. + * @returns {number} - The length of the buffer. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * let length = b.Len(); + */ + Len() { + // implemented in go + }; + + /** + * @method + * @description Pack uses structs.Pack and packs given data and appends it to the buffer. It packs the data according to the given format. + * @param {string} formatStr - The format string to pack the data. + * @param {string} msg - The message to pack. + * @returns {Buffer} - The buffer after packing the data. + * @throws {error} - The error encountered during packing. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * b.Pack('format', 'message'); + */ + Pack(formatStr, msg) { + // implemented in go + }; + + /** + * @method + * @description String returns the string representation of the buffer. + * @returns {string} - The string representation of the buffer. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * let str = b.String(); + */ + String() { + // implemented in go + }; + + /** + * @method + * @description Write appends a byte slice to the buffer. + * @param {Uint8Array} data - The byte slice to append to the buffer. + * @returns {Buffer} - The buffer after appending the byte slice. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * b.Write(new Uint8Array([1, 2, 3])); + */ + Write(data) { + // implemented in go + }; + + /** + * @method + * @description WriteString appends a string to the buffer. + * @param {string} data - The string to append to the buffer. + * @returns {Buffer} - The buffer after appending the string. + * @example + * let m = require('nuclei/bytes'); + * let b = m.Buffer(); + * b.WriteString('data'); + */ + WriteString(data) { + // implemented in go + }; +}; + +/** + * @function + * @description NewBuffer creates a new buffer from a byte slice. + * @param {Uint8Array} call - The byte slice to create the buffer from. + * @returns {Buffer} - The new buffer created from the byte slice. + * @example + * let m = require('nuclei/bytes'); + * let buffer = m.NewBuffer(new Uint8Array([1, 2, 3])); + */ +function NewBuffer(call) { + // implemented in go +}; + +module.exports = { + Buffer: Buffer, + NewBuffer: NewBuffer, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libfs/fs.js b/v2/pkg/js/generated/js/libfs/fs.js new file mode 100644 index 00000000..026c07ab --- /dev/null +++ b/v2/pkg/js/generated/js/libfs/fs.js @@ -0,0 +1,65 @@ +/** @module fs */ + +/** + * @function + * @description ListDir lists all files and directories within a path depending on the itemType provided. itemType can be any one of ['file','dir','all'] + * @param {string} path - The path to list files and directories from. + * @param {string} itemType - The type of items to list. Can be 'file', 'dir', or 'all'. + * @returns {string[]} - The list of files and directories. + * @throws {error} - The error encountered during listing. + * @example + * let m = require('nuclei/fs'); + * let items = m.ListDir('/tmp', 'all'); + */ +function ListDir(path, itemType) { + // implemented in go +}; + +/** + * @function + * @description ReadFile reads file contents within permitted paths + * @param {string} path - The path to the file to read. + * @returns {Uint8Array} - The contents of the file. + * @throws {error} - The error encountered during reading. + * @example + * let m = require('nuclei/fs'); + * let content = m.ReadFile('/tmp/myfile.txt'); + */ +function ReadFile(path) { + // implemented in go +}; + +/** + * @function + * @description ReadFileAsString reads file contents within permitted paths and returns content as string + * @param {string} path - The path to the file to read. + * @returns {string} - The contents of the file as a string. + * @throws {error} - The error encountered during reading. + * @example + * let m = require('nuclei/fs'); + * let content = m.ReadFileAsString('/tmp/myfile.txt'); + */ +function ReadFileAsString(path) { + // implemented in go +}; + +/** + * @function + * @description ReadFilesFromDir reads all files from a directory and returns a array with file contents of all files + * @param {string} dir - The directory to read files from. + * @returns {string[]} - The contents of all files in the directory. + * @throws {error} - The error encountered during reading. + * @example + * let m = require('nuclei/fs'); + * let contentArray = m.ReadFilesFromDir('/tmp'); + */ +function ReadFilesFromDir(dir) { + // implemented in go +}; + +module.exports = { + ListDir: ListDir, + ReadFile: ReadFile, + ReadFileAsString: ReadFileAsString, + ReadFilesFromDir: ReadFilesFromDir, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libgoconsole/goconsole.js b/v2/pkg/js/generated/js/libgoconsole/goconsole.js new file mode 100644 index 00000000..deeddd97 --- /dev/null +++ b/v2/pkg/js/generated/js/libgoconsole/goconsole.js @@ -0,0 +1,63 @@ +/** @module goconsole */ + +/** + * @class + * @classdesc GoConsolePrinter is a console printer for nuclei using gologger + */ +class GoConsolePrinter { + /** + * @method + * @description Error logs an error message + * @param {string} msg - The message to log. + * @example + * let m = require('nuclei/goconsole'); + * let c = m.GoConsolePrinter(); + * c.Error('This is an error message'); + */ + Error(msg) { + // implemented in go + }; + + /** + * @method + * @description Log logs a message + * @param {string} msg - The message to log. + * @example + * let m = require('nuclei/goconsole'); + * let c = m.GoConsolePrinter(); + * c.Log('This is a log message'); + */ + Log(msg) { + // implemented in go + }; + + /** + * @method + * @description Warn logs a warning message + * @param {string} msg - The message to log. + * @example + * let m = require('nuclei/goconsole'); + * let c = m.GoConsolePrinter(); + * c.Warn('This is a warning message'); + */ + Warn(msg) { + // implemented in go + }; +}; + +/** + * @function + * @description NewGoConsolePrinter creates a new instance of GoConsolePrinter + * @returns {GoConsolePrinter} - The new instance of GoConsolePrinter. + * @example + * let m = require('nuclei/goconsole'); + * let printer = m.NewGoConsolePrinter(); + */ +function NewGoConsolePrinter() { + // implemented in go +}; + +module.exports = { + GoConsolePrinter: GoConsolePrinter, + NewGoConsolePrinter: NewGoConsolePrinter, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libikev2/ikev2.js b/v2/pkg/js/generated/js/libikev2/ikev2.js new file mode 100644 index 00000000..46fda801 --- /dev/null +++ b/v2/pkg/js/generated/js/libikev2/ikev2.js @@ -0,0 +1,38 @@ +/** @module ikev2 */ + +/** + * @class + * @classdesc IKEMessage is the IKEv2 message. IKEv2 implements a limited subset of IKEv2 Protocol, specifically the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange. + */ +class IKEMessage { + /** + * @method + * @description AppendPayload appends a payload to the IKE message + * @param {object} payload - The payload to append to the IKE message. + * @example + * let m = require('nuclei/ikev2'); + * let ike = m.IKEMessage(); + * ike.AppendPayload({data: 'test'}); + */ + AppendPayload(payload) { + // implemented in go + }; + + /** + * @method + * @description Encode encodes the final IKE message + * @returns {Uint8Array} - The encoded IKE message. + * @throws {error} - The error encountered during encoding. + * @example + * let m = require('nuclei/ikev2'); + * let ike = m.IKEMessage(); + * let encoded = ike.Encode(); + */ + Encode() { + // implemented in go + }; +}; + +module.exports = { + IKEMessage: IKEMessage, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libkerberos/kerberos.js b/v2/pkg/js/generated/js/libkerberos/kerberos.js new file mode 100644 index 00000000..4f737705 --- /dev/null +++ b/v2/pkg/js/generated/js/libkerberos/kerberos.js @@ -0,0 +1,34 @@ +/** @module kerberos */ + +/** + * @class + * @classdesc KerberosClient is a kerberos client + */ +class KerberosClient { + /** + * @method + * @description EnumerateUser returns true if the user exists in the domain. If the user is not found, false is returned. If the user is found, true is returned. Optionally, the AS-REP hash is also returned if discovered. + * @param {string} domain - The domain to check. + * @param {string} controller - The controller to use. + * @param {string} username - The username to check. + * @returns {EnumerateUserResponse} - The response of the enumeration. + * @throws {error} - The error encountered during enumeration. + * @example + * let m = require('nuclei/kerberos'); + * let c = m.KerberosClient(); + * let response = c.EnumerateUser('domain', 'controller', 'username'); + */ + EnumerateUser(domain, controller, username) { + // implemented in go + }; +}; + +/** + * @typedef {object} EnumerateUserResponse + * @description EnumerateUserResponse is the response object from the EnumerateUser method. + */ +const EnumerateUserResponse = {}; + +module.exports = { + KerberosClient: KerberosClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libldap/ldap.js b/v2/pkg/js/generated/js/libldap/ldap.js new file mode 100644 index 00000000..9297b923 --- /dev/null +++ b/v2/pkg/js/generated/js/libldap/ldap.js @@ -0,0 +1,49 @@ +/** @module ldap */ + +/** + * @typedef {object} LDAPMetadata + * @description LDAPMetadata is an object containing metadata from ldap server. + */ +const LDAPMetadata = {}; + +/** + * @class + * @classdesc LdapClient is a client for ldap protocol in golang. It is a wrapper around the standard library ldap package. + */ +class LdapClient { + /** + * @method + * @description CollectLdapMetadata collects metadata from ldap server. + * @param {string} domain - The domain to collect metadata from. + * @param {string} controller - The controller to collect metadata from. + * @returns {LDAPMetadata} - The metadata from ldap server. + * @throws {error} - The error encountered during metadata collection. + * @example + * let m = require('nuclei/ldap'); + * let c = m.LdapClient(); + * let metadata = c.CollectLdapMetadata('example.com', 'controller1'); + */ + CollectLdapMetadata(domain, controller) { + // implemented in go + }; + + /** + * @method + * @description IsLdap checks if the given host and port are running ldap server. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {boolean} - Whether the given host and port are running ldap server. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/ldap'); + * let c = m.LdapClient(); + * let isLdap = c.IsLdap('localhost', 389); + * */ + IsLdap(host, port) { + // implemented in go + }; +}; + +module.exports = { + LdapClient: LdapClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libmssql/mssql.js b/v2/pkg/js/generated/js/libmssql/mssql.js new file mode 100644 index 00000000..81f5a32d --- /dev/null +++ b/v2/pkg/js/generated/js/libmssql/mssql.js @@ -0,0 +1,64 @@ +/** @module mssql */ + +/** + * @class + * @classdesc MSSQLClient is a client for MS SQL database. Internally client uses denisenkom/go-mssqldb driver. + */ +class MSSQLClient { + /** + * @method + * @description Connect connects to MS SQL database using given credentials. If connection is successful, it returns true. If connection is unsuccessful, it returns false and error. The connection is closed after the function returns. + * @param {string} host - The host of the MS SQL database. + * @param {int} port - The port of the MS SQL database. + * @param {string} username - The username to connect to the MS SQL database. + * @param {string} password - The password to connect to the MS SQL database. + * @returns {bool} - The status of the connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/mssql'); + * let c = m.MSSQLClient(); + * let isConnected = c.Connect('localhost', 1433, 'username', 'password'); + */ + Connect(host, port, username, password) { + // implemented in go + }; + + /** + * @method + * @description ConnectWithDB connects to MS SQL database using given credentials and database name. If connection is successful, it returns true. If connection is unsuccessful, it returns false and error. The connection is closed after the function returns. + * @param {string} host - The host of the MS SQL database. + * @param {int} port - The port of the MS SQL database. + * @param {string} username - The username to connect to the MS SQL database. + * @param {string} password - The password to connect to the MS SQL database. + * @param {string} dbName - The name of the database to connect to. + * @returns {bool} - The status of the connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/mssql'); + * let c = m.MSSQLClient(); + * let isConnected = c.ConnectWithDB('localhost', 1433, 'username', 'password', 'myDatabase'); + */ + ConnectWithDB(host, port, username, password, dbName) { + // implemented in go + }; + + /** + * @method + * @description IsMssql checks if the given host is running MS SQL database. If the host is running MS SQL database, it returns true. If the host is not running MS SQL database, it returns false. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {bool} - The status of the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/mssql'); + * let c = m.MSSQLClient(); + * let isMssql = c.IsMssql('localhost', 1433); + */ + IsMssql(host, port) { + // implemented in go + }; +}; + +module.exports = { + MSSQLClient: MSSQLClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libmysql/mysql.js b/v2/pkg/js/generated/js/libmysql/mysql.js new file mode 100644 index 00000000..afa5a22e --- /dev/null +++ b/v2/pkg/js/generated/js/libmysql/mysql.js @@ -0,0 +1,84 @@ +/** @module mysql */ + +/** + * @class + * @classdesc MySQLClient is a client for MySQL database. Internally client uses go-sql-driver/mysql driver. + */ +class MySQLClient { + /** + * @method + * @description Connect connects to MySQL database using given credentials. If connection is successful, it returns true. If connection is unsuccessful, it returns false and error. The connection is closed after the function returns. + * @param {string} host - The host of the MySQL database. + * @param {int} port - The port of the MySQL database. + * @param {string} username - The username to connect to the MySQL database. + * @param {string} password - The password to connect to the MySQL database. + * @returns {bool} - The result of the connection attempt. + * @throws {error} - The error encountered during connection attempt. + * @example + * let m = require('nuclei/mysql'); + * let c = m.MySQLClient(); + * let result = c.Connect('localhost', 3306, 'root', 'password'); + */ + Connect(host, port, username, password) { + // implemented in go + }; + + /** + * @method + * @description ConnectWithDB connects to MySQL database using given credentials and database name. If connection is successful, it returns true. If connection is unsuccessful, it returns false and error. The connection is closed after the function returns. + * @param {string} host - The host of the MySQL database. + * @param {int} port - The port of the MySQL database. + * @param {string} username - The username to connect to the MySQL database. + * @param {string} password - The password to connect to the MySQL database. + * @param {string} dbName - The name of the database to connect to. + * @returns {bool} - The result of the connection attempt. + * @throws {error} - The error encountered during connection attempt. + * @example + * let m = require('nuclei/mysql'); + * let c = m.MySQLClient(); + * let result = c.ConnectWithDB('localhost', 3306, 'root', 'password', 'mydb'); + */ + ConnectWithDB(host, port, username, password, dbName) { + // implemented in go + }; + + /** + * @method + * @description ExecuteQuery connects to Mysql database using given credentials and database name and executes a query on the db. + * @param {string} host - The host of the MySQL database. + * @param {int} port - The port of the MySQL database. + * @param {string} username - The username to connect to the MySQL database. + * @param {string} password - The password to connect to the MySQL database. + * @param {string} dbName - The name of the database to connect to. + * @param {string} query - The query to execute on the database. + * @returns {string} - The result of the query execution. + * @throws {error} - The error encountered during query execution. + * @example + * let m = require('nuclei/mysql'); + * let c = m.MySQLClient(); + * let result = c.ExecuteQuery('localhost', 3306, 'root', 'password', 'mydb', 'SELECT * FROM users'); + */ + ExecuteQuery(host, port, username, password, dbName, query) { + // implemented in go + }; + + /** + * @method + * @description IsMySQL checks if the given host is running MySQL database. If the host is running MySQL database, it returns true. If the host is not running MySQL database, it returns false. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {bool} - The result of the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/mysql'); + * let c = m.MySQLClient(); + * let result = c.IsMySQL('localhost', 3306); + */ + IsMySQL(host, port) { + // implemented in go + }; +}; + +module.exports = { + MySQLClient: MySQLClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libnet/net.js b/v2/pkg/js/generated/js/libnet/net.js new file mode 100644 index 00000000..50b84e35 --- /dev/null +++ b/v2/pkg/js/generated/js/libnet/net.js @@ -0,0 +1,156 @@ +/**@module net */ + +/** + * @class + * @classdesc NetConn is a connection to a remote host. + */ +class NetConn { + /** + * @method + * @description Close closes the connection. + * @throws {error} - The error encountered during connection closing. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * c.Close(); + */ + Close() { + // implemented in go + }; + + /** + * @method + * @description Recv receives data from the connection with a timeout. If N is 0, it will read up to 4096 bytes. + * @param {number} N - The number of bytes to receive. + * @returns {Uint8Array} - The received data in an array. + * @throws {error} - The error encountered during data receiving. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * let data = c.Recv(1024); + */ + Recv(N) { + // implemented in go + }; + + /** + * @method + * @description RecvHex receives data from the connection with a timeout in hex format. If N is 0, it will read up to 4096 bytes. + * @param {number} N - The number of bytes to receive. + * @returns {string} - The received data in hex format. + * @throws {error} - The error encountered during data receiving. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * let data = c.RecvHex(1024); + */ + RecvHex(N) { + // implemented in go + }; + + /** + * @method + * @description RecvString receives data from the connection with a timeout. Output is returned as a string. If N is 0, it will read up to 4096 bytes. + * @param {number} N - The number of bytes to receive. + * @returns {string} - The received data as a string. + * @throws {error} - The error encountered during data receiving. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * let data = c.RecvString(1024); + */ + RecvString(N) { + // implemented in go + }; + + /** + * @method + * @description Send sends data to the connection with a timeout. + * @param {Uint8Array} data - The data to send. + * @throws {error} - The error encountered during data sending. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * c.Send(new Uint8Array([1, 2, 3])); + */ + Send(data) { + // implemented in go + }; + + /** + * @method + * @description SendArray sends array data to connection. + * @param {Uint8Array} data - The array data to send. + * @throws {error} - The error encountered during data sending. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * c.SendArray(new Uint8Array([1, 2, 3])); + */ + SendArray(data) { + // implemented in go + }; + + /** + * @method + * @description SendHex sends hex data to connection. + * @param {string} data - The hex data to send. + * @throws {error} - The error encountered during data sending. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * c.SendHex('0x123'); + */ + SendHex(data) { + // implemented in go + }; + + /** + * @method + * @description SetTimeout sets read/write timeout for the connection (in seconds). + * @param {number} value - The timeout value in seconds. + * @example + * let m = require('nuclei/net'); + * let c = m.Open('tcp', 'localhost:8080'); + * c.SetTimeout(5); + */ + SetTimeout(value) { + // implemented in go + }; +}; + +/** + * @function + * @description Open opens a new connection to the address with a timeout. Supported protocols: tcp, udp. + * @param {string} protocol - The protocol to use. + * @param {string} address - The address to connect to. + * @returns {NetConn} - The NetConn object representing the connection. + * @throws {error} - The error encountered during connection opening. + * @example + * let m = require('nuclei/net'); + * let conn = m.Open('tcp', 'localhost:8080'); + */ +function Open(protocol, address) { + // implemented in go +}; + +/** + * @function + * @description OpenTLS opens a new connection to the address with a timeout. Supported protocols: tcp, udp. + * @param {string} protocol - The protocol to use. + * @param {string} address - The address to connect to. + * @returns {NetConn} - The NetConn object representing the connection. + * @throws {error} - The error encountered during connection opening. + * @example + * let m = require('nuclei/net'); + * let conn = m.OpenTLS('tcp', 'localhost:8080'); + */ +function OpenTLS(protocol, address) { + // implemented in go +}; + +module.exports = { + NetConn: NetConn, + Open: Open, + OpenTLS: OpenTLS, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/liboracle/oracle.js b/v2/pkg/js/generated/js/liboracle/oracle.js new file mode 100644 index 00000000..2341342d --- /dev/null +++ b/v2/pkg/js/generated/js/liboracle/oracle.js @@ -0,0 +1,33 @@ +/** @module oracle */ + +/** + * @class + * @classdesc OracleClient is a minimal Oracle client for nuclei scripts. + */ +class OracleClient { + /** + * @method + * @description IsOracle checks if a host is running an Oracle server. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {IsOracleResponse} - The response from the Oracle server. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/oracle'); + * let c = m.OracleClient(); + * let response = c.IsOracle('localhost', 1521); + */ + IsOracle(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsOracleResponse + * @description IsOracleResponse is an object containing the response from the Oracle server. + */ +const IsOracleResponse = {}; + +module.exports = { + OracleClient: OracleClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libpop3/pop3.js b/v2/pkg/js/generated/js/libpop3/pop3.js new file mode 100644 index 00000000..595efa3f --- /dev/null +++ b/v2/pkg/js/generated/js/libpop3/pop3.js @@ -0,0 +1,33 @@ +/** @module pop3 */ + +/** + * @class + * @classdesc Pop3Client is a minimal POP3 client for nuclei scripts + */ +class Pop3Client { + /** + * @method + * @description IsPOP3 checks if a host is running a POP3 server + * @param {string} host - The host to check. + * @param {number} port - The port to check. + * @returns {IsPOP3Response} - The response of the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/pop3'); + * let c = m.Pop3Client(); + * let response = c.IsPOP3('localhost', 110); + */ + IsPOP3(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsPOP3Response + * @description IsPOP3Response is an object containing the response of the IsPOP3 check. + */ +const IsPOP3Response = {}; + +module.exports = { + Pop3Client: Pop3Client, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libpostgres/postgres.js b/v2/pkg/js/generated/js/libpostgres/postgres.js new file mode 100644 index 00000000..71588f6b --- /dev/null +++ b/v2/pkg/js/generated/js/libpostgres/postgres.js @@ -0,0 +1,84 @@ +/** @module postgres */ + +/** + * @class + * @classdesc PGClient is a client for Postgres database. Internally client uses go-pg/pg driver. + */ +class PGClient { + /** + * @method + * @description Connect connects to Postgres database using given credentials. The connection is closed after the function returns. + * @param {string} host - The host of the Postgres database. + * @param {int} port - The port of the Postgres database. + * @param {string} username - The username to connect to the Postgres database. + * @param {string} password - The password to connect to the Postgres database. + * @returns {bool} - If connection is successful, it returns true. + * @throws {error} - If connection is unsuccessful, it returns the error. + * @example + * let m = require('nuclei/postgres'); + * let c = m.PGClient(); + * let isConnected = c.Connect('localhost', 5432, 'username', 'password'); + */ + Connect(host, port, username, password) { + // implemented in go + }; + + /** + * @method + * @description ConnectWithDB connects to Postgres database using given credentials and database name. The connection is closed after the function returns. + * @param {string} host - The host of the Postgres database. + * @param {int} port - The port of the Postgres database. + * @param {string} username - The username to connect to the Postgres database. + * @param {string} password - The password to connect to the Postgres database. + * @param {string} dbName - The name of the database to connect to. + * @returns {bool} - If connection is successful, it returns true. + * @throws {error} - If connection is unsuccessful, it returns the error. + * @example + * let m = require('nuclei/postgres'); + * let c = m.PGClient(); + * let isConnected = c.ConnectWithDB('localhost', 5432, 'username', 'password', 'mydb'); + */ + ConnectWithDB(host, port, username, password, dbName) { + // implemented in go + }; + + /** + * @method + * @description ExecuteQuery connects to Postgres database using given credentials and database name and executes a query on the db. + * @param {string} host - The host of the Postgres database. + * @param {int} port - The port of the Postgres database. + * @param {string} username - The username to connect to the Postgres database. + * @param {string} password - The password to connect to the Postgres database. + * @param {string} dbName - The name of the database to connect to. + * @param {string} query - The query to execute on the database. + * @returns {string} - The result of the query execution. + * @throws {error} - If query execution is unsuccessful, it returns the error. + * @example + * let m = require('nuclei/postgres'); + * let c = m.PGClient(); + * let result = c.ExecuteQuery('localhost', 5432, 'username', 'password', 'mydb', 'SELECT * FROM users'); + */ + ExecuteQuery(host, port, username, password, dbName, query) { + // implemented in go + }; + + /** + * @method + * @description IsPostgres checks if the given host and port are running Postgres database. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {bool} - If the host and port are running Postgres database, it returns true. + * @throws {error} - If the check is unsuccessful, it returns the error. + * @example + * let m = require('nuclei/postgres'); + * let c = m.PGClient(); + * let isPostgres = c.IsPostgres('localhost', 5432); + */ + IsPostgres(host, port) { + // implemented in go + }; +}; + +module.exports = { + PGClient: PGClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/librdp/rdp.js b/v2/pkg/js/generated/js/librdp/rdp.js new file mode 100644 index 00000000..456837dc --- /dev/null +++ b/v2/pkg/js/generated/js/librdp/rdp.js @@ -0,0 +1,55 @@ +/**@module rdp */ + +/** + * @class + * @classdesc RDPClient is a client for rdp servers + */ +class RDPClient { + /** + * @method + * @description CheckRDPAuth checks if the given host and port are running rdp server with authentication and returns their metadata. + * @param {string} host - The host to check. + * @param {number} port - The port to check. + * @returns {CheckRDPAuthResponse} - The response from the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/rdp'); + * let c = m.RDPClient(); + * let response = c.CheckRDPAuth('localhost', 3389); + */ + CheckRDPAuth(host, port) { + // implemented in go + }; + + /** + * @method + * @description IsRDP checks if the given host and port are running rdp server. If connection is successful, it returns true. If connection is unsuccessful, it returns false and error. The Name of the OS is also returned if the connection is successful. + * @param {string} host - The host to check. + * @param {number} port - The port to check. + * @returns {IsRDPResponse} - The response from the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/rdp'); + * let c = m.RDPClient(); + * let response = c.IsRDP('localhost', 3389); + */ + IsRDP(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} CheckRDPAuthResponse + * @description CheckRDPAuthResponse is the response from the CheckRDPAuth method. + */ +const CheckRDPAuthResponse = {}; + +/** + * @typedef {object} IsRDPResponse + * @description IsRDPResponse is the response from the IsRDP method. + */ +const IsRDPResponse = {}; + +module.exports = { + RDPClient: RDPClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libredis/redis.js b/v2/pkg/js/generated/js/libredis/redis.js new file mode 100644 index 00000000..bbff60b4 --- /dev/null +++ b/v2/pkg/js/generated/js/libredis/redis.js @@ -0,0 +1,87 @@ +/** @module redis */ + +/** + * @function + * @description Connect tries to connect redis server with password + * @param {string} host - The host of the redis server. + * @param {number} port - The port of the redis server. + * @param {string} password - The password for the redis server. + * @returns {boolean} - The status of the connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/redis'); + * let status = m.Connect('localhost', 6379, 'password'); + */ +function Connect(host, port, password) { + // implemented in go +}; + +/** + * @function + * @description GetServerInfo returns the server info for a redis server + * @param {string} host - The host of the redis server. + * @param {number} port - The port of the redis server. + * @returns {string} - The server info. + * @throws {error} - The error encountered during getting server info. + * @example + * let m = require('nuclei/redis'); + * let info = m.GetServerInfo('localhost', 6379); + */ +function GetServerInfo(host, port) { + // implemented in go +}; + +/** + * @function + * @description GetServerInfoAuth returns the server info for a redis server + * @param {string} host - The host of the redis server. + * @param {number} port - The port of the redis server. + * @param {string} password - The password for the redis server. + * @returns {string} - The server info. + * @throws {error} - The error encountered during getting server info. + * @example + * let m = require('nuclei/redis'); + * let info = m.GetServerInfoAuth('localhost', 6379, 'password'); + */ +function GetServerInfoAuth(host, port, password) { + // implemented in go +}; + +/** + * @function + * @description IsAuthenticated checks if the redis server requires authentication + * @param {string} host - The host of the redis server. + * @param {number} port - The port of the redis server. + * @returns {boolean} - The authentication status. + * @throws {error} - The error encountered during checking authentication. + * @example + * let m = require('nuclei/redis'); + * let isAuthenticated = m.IsAuthenticated('localhost', 6379); + */ +function IsAuthenticated(host, port) { + // implemented in go +}; + +/** + * @function + * @description RunLuaScript runs a lua script on the redis server + * @param {string} host - The host of the redis server. + * @param {number} port - The port of the redis server. + * @param {string} password - The password for the redis server. + * @param {string} script - The lua script to run. + * @throws {error} - The error encountered during running the lua script. + * @example + * let m = require('nuclei/redis'); + * m.RunLuaScript('localhost', 6379, 'password', 'return redis.call(\'ping\')'); + */ +function RunLuaScript(host, port, password, script) { + // implemented in go +}; + +module.exports = { + Connect: Connect, + GetServerInfo: GetServerInfo, + GetServerInfoAuth: GetServerInfoAuth, + IsAuthenticated: IsAuthenticated, + RunLuaScript: RunLuaScript, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/librsync/rsync.js b/v2/pkg/js/generated/js/librsync/rsync.js new file mode 100644 index 00000000..c5d1271d --- /dev/null +++ b/v2/pkg/js/generated/js/librsync/rsync.js @@ -0,0 +1,33 @@ +/** @module rsync */ + +/** + * @class + * @classdesc RsyncClient is a minimal Rsync client for nuclei scripts. + */ +class RsyncClient { + /** + * @method + * @description IsRsync checks if a host is running a Rsync server. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {IsRsyncResponse} - The response from the IsRsync check. + * @throws {error} - The error encountered during the IsRsync check. + * @example + * let m = require('nuclei/rsync'); + * let c = m.RsyncClient(); + * let response = c.IsRsync('localhost', 22); + */ + IsRsync(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsRsyncResponse + * @description IsRsyncResponse is an object containing the response from the IsRsync check. + */ +const IsRsyncResponse = {}; + +module.exports = { + RsyncClient: RsyncClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libsmb/smb.js b/v2/pkg/js/generated/js/libsmb/smb.js new file mode 100644 index 00000000..a14333a5 --- /dev/null +++ b/v2/pkg/js/generated/js/libsmb/smb.js @@ -0,0 +1,90 @@ +/** @module smb */ + +/** + * @typedef {object} SMBLog + * @description SMBLog is an object containing the log of the SMB handshake. + */ +const SMBLog = {}; + +/** + * @typedef {object} ServiceSMB + * @description ServiceSMB is an object containing the metadata of the SMBv2 service. + */ + +const ServiceSMB = {}; + +/** + * @class + * @classdesc SMBClient is a client for SMB servers. + */ +class SMBClient { + /** + * @method + * @description ConnectSMBInfoMode tries to connect to provided host and port and discover SMB information + * @param {string} host - The host to connect to. + * @param {string} port - The port to connect to. + * @returns {SMBLog} - The log of the SMB handshake. + * @throws {error} - The error encountered during the connection. + * @example + * let m = require('nuclei/smb'); + * let c = m.SMBClient(); + * let log = c.ConnectSMBInfoMode('localhost', '445'); + */ + ConnectSMBInfoMode(host, port) { + // implemented in go + }; + + /** + * @method + * @description DetectSMBGhost tries to detect SMBGhost vulnerability by using SMBv3 compression feature. + * @param {string} host - The host to connect to. + * @param {string} port - The port to connect to. + * @returns {boolean} - The result of the SMBGhost vulnerability detection. + * @throws {error} - The error encountered during the detection. + * @example + * let m = require('nuclei/smb'); + * let c = m.SMBClient(); + * let isVulnerable = c.DetectSMBGhost('localhost', '445'); + */ + DetectSMBGhost(host, port) { + // implemented in go + }; + + /** + * @method + * @description ListSMBv2Metadata tries to connect to provided host and port and list SMBv2 metadata. + * @param {string} host - The host to connect to. + * @param {string} port - The port to connect to. + * @returns {ServiceSMB} - The metadata of the SMBv2 service. + * @throws {error} - The error encountered during the listing. + * @example + * let m = require('nuclei/smb'); + * let c = m.SMBClient(); + * let metadata = c.ListSMBv2Metadata('localhost', '445'); + */ + ListSMBv2Metadata(host, port) { + // implemented in go + }; + + /** + * @method + * @description ListShares tries to connect to provided host and port and list shares by using given credentials. + * @param {string} host - The host to connect to. + * @param {string} port - The port to connect to. + * @param {string} user - The username for authentication. + * @param {string} password - The password for authentication. + * @returns {string[]} - The list of shares. + * @throws {error} - The error encountered during the listing. + * @example + * let m = require('nuclei/smb'); + * let c = m.SMBClient(); + * let shares = c.ListShares('localhost', '445', 'user', 'password'); + */ + ListShares(host, port, user, password) { + // implemented in go + }; +}; + +module.exports = { + SMBClient: SMBClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libsmtp/smtp.js b/v2/pkg/js/generated/js/libsmtp/smtp.js new file mode 100644 index 00000000..f07c67e1 --- /dev/null +++ b/v2/pkg/js/generated/js/libsmtp/smtp.js @@ -0,0 +1,33 @@ +/** @module smtp */ + +/** + * @class + * @classdesc SMTPClient is a minimal SMTP client for nuclei scripts. + */ +class SMTPClient { + /** + * @method + * @description IsSMTP checks if a host is running a SMTP server. + * @param {string} host - The host to check. + * @param {int} port - The port to check. + * @returns {IsSMTPResponse} - The response of the check. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/smtp'); + * let c = m.SMTPClient(); + * let response = c.IsSMTP('localhost', 25); + */ + IsSMTP(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsSMTPResponse + * @description IsSMTPResponse is an object containing the response of the IsSMTP check. + */ +const IsSMTPResponse = {}; + +module.exports = { + SMTPClient: SMTPClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libssh/ssh.js b/v2/pkg/js/generated/js/libssh/ssh.js new file mode 100644 index 00000000..81294f0f --- /dev/null +++ b/v2/pkg/js/generated/js/libssh/ssh.js @@ -0,0 +1,69 @@ +/** @module ssh */ + +/** + * @class + * @classdesc SSHClient is a client for SSH servers. Internally client uses github.com/zmap/zgrab2/lib/ssh driver. + */ +class SSHClient { + /** + * @method + * @description Connect tries to connect to provided host and port with provided username and password with ssh. Returns state of connection and error. If error is not nil, state will be false. + * @param {string} host - The host to connect to. + * @param {number} port - The port to connect to. + * @param {string} username - The username to use for connection. + * @param {string} password - The password to use for connection. + * @returns {boolean} - The state of the connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/ssh'); + * let c = m.SSHClient(); + * let state = c.Connect('localhost', 22, 'user', 'password'); + */ + Connect(host, port, username, password) { + // implemented in go + }; + + /** + * @method + * @description ConnectSSHInfoMode tries to connect to provided host and port. Returns HandshakeLog and error. If error is not nil, state will be false. HandshakeLog is a struct that contains information about the ssh connection. + * @param {string} host - The host to connect to. + * @param {number} port - The port to connect to. + * @returns {HandshakeLog} - The HandshakeLog object containing information about the ssh connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/ssh'); + * let c = m.SSHClient(); + * let log = c.ConnectSSHInfoMode('localhost', 22); + */ + ConnectSSHInfoMode(host, port) { + // implemented in go + }; + + /** + * @method + * @description ConnectWithKey tries to connect to provided host and port with provided username and private_key. Returns state of connection and error. If error is not nil, state will be false. + * @param {string} host - The host to connect to. + * @param {number} port - The port to connect to. + * @param {string} username - The username to use for connection. + * @param {string} key - The private key to use for connection. + * @returns {boolean} - The state of the connection. + * @throws {error} - The error encountered during connection. + * @example + * let m = require('nuclei/ssh'); + * let c = m.SSHClient(); + * let state = c.ConnectWithKey('localhost', 22, 'user', 'key'); + */ + ConnectWithKey(host, port, username, key) { + // implemented in go + }; +}; + +/** + * @typedef {object} HandshakeLog + * @description HandshakeLog is a object containing information about the ssh connection. + */ +const HandshakeLog = {}; + +module.exports = { + SSHClient: SSHClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libstructs/structs.js b/v2/pkg/js/generated/js/libstructs/structs.js new file mode 100644 index 00000000..0ec94de2 --- /dev/null +++ b/v2/pkg/js/generated/js/libstructs/structs.js @@ -0,0 +1,54 @@ +/**@module structs */ + +/** + * @function + * @description Pack returns a byte slice containing the values of msg slice packed according to the given format. + * The items of msg slice must match the values required by the format exactly. + * @param {string} formatStr - The format string. + * @param {any[]} msg - The message to be packed. + * @returns {Uint8Array} - The packed message in a byte array. + * @throws {error} - The error encountered during packing. + * @example + * let s = require('nuclei/structs'); + * let packedMsg = s.Pack("H", [0]); + */ +function Pack(formatStr, msg) { + // implemented in go +}; + +/** + * @function + * @description StructsCalcSize returns the number of bytes needed to pack the values according to the given format. + * @param {string} format - The format string. + * @returns {number} - The number of bytes needed to pack the values. + * @throws {error} - The error encountered during calculation. + * @example + * let s = require('nuclei/structs'); + * let size = s.StructsCalcSize("H"); + */ +function StructsCalcSize(format) { + // implemented in go +}; + +/** + * @function + * @description Unpack the byte slice (presumably packed by Pack(format, msg)) according to the given format. + * The result is a []interface{} slice even if it contains exactly one item. + * The byte slice must contain not less the amount of data required by the format + * (len(msg) must more or equal CalcSize(format)). + * @param {string} format - The format string. + * @param {Uint8Array} msg - The packed message to be unpacked. + * @throws {error} - The error encountered during unpacking. + * @example + * let s = require('nuclei/structs'); + * let unpackedMsg = s.Unpack(">I", buff[:nb]); + */ +function Unpack(format, msg) { + // implemented in go +}; + +module.exports = { + Pack: Pack, + StructsCalcSize: StructsCalcSize, + Unpack: Unpack, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libtelnet/telnet.js b/v2/pkg/js/generated/js/libtelnet/telnet.js new file mode 100644 index 00000000..4217eb0e --- /dev/null +++ b/v2/pkg/js/generated/js/libtelnet/telnet.js @@ -0,0 +1,33 @@ +/** @module telnet */ + +/** + * @class + * @classdesc TelnetClient is a minimal Telnet client for nuclei scripts + */ +class TelnetClient { + /** + * @method + * @description IsTelnet checks if a host is running a Telnet server + * @param {string} host - The host to check for Telnet server. + * @param {int} port - The port to check for Telnet server. + * @returns {IsTelnetResponse} - The response of the IsTelnet check. + * @throws {error} - The error encountered during the IsTelnet check. + * @example + * let m = require('nuclei/telnet'); + * let c = m.TelnetClient(); + * let response = c.IsTelnet('localhost', 23); + */ + IsTelnet(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsTelnetResponse + * @description IsTelnetResponse is an object containing the response of the IsTelnet check. + */ +const IsTelnetResponse = {}; + +module.exports = { + TelnetClient: TelnetClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/generated/js/libvnc/vnc.js b/v2/pkg/js/generated/js/libvnc/vnc.js new file mode 100644 index 00000000..4afd1eeb --- /dev/null +++ b/v2/pkg/js/generated/js/libvnc/vnc.js @@ -0,0 +1,33 @@ +/** @module vnc */ + +/** + * @class + * @classdesc VNCClient is a minimal VNC client for nuclei scripts. + */ +class VNCClient { + /** + * @method + * @description IsVNC checks if a host is running a VNC server. + * @param {string} host - The host to check. + * @param {number} port - The port to check. + * @returns {IsVNCResponse} - The response indicating if the host is running a VNC server and the banner of the VNC server. + * @throws {error} - The error encountered during the check. + * @example + * let m = require('nuclei/vnc'); + * let c = m.VNCClient(); + * let response = c.IsVNC('localhost', 5900); + */ + IsVNC(host, port) { + // implemented in go + }; +}; + +/** + * @typedef {object} IsVNCResponse + * @description IsVNCResponse is an object containing the response of the IsVNC method. + */ +const IsVNCResponse = {}; + +module.exports = { + VNCClient: VNCClient, +}; \ No newline at end of file diff --git a/v2/pkg/js/global/exports.js b/v2/pkg/js/global/exports.js new file mode 100644 index 00000000..8cd80c0b --- /dev/null +++ b/v2/pkg/js/global/exports.js @@ -0,0 +1,10 @@ +exports = { + // General + dump_json: dump_json, + to_json: to_json, + to_array: to_array, + hex_to_ascii: hex_to_ascii, + + // Active Directory + getDomainControllerName: getDomainControllerName, +}; diff --git a/v2/pkg/js/global/js/active_directory.js b/v2/pkg/js/global/js/active_directory.js new file mode 100644 index 00000000..22a3e7f6 --- /dev/null +++ b/v2/pkg/js/global/js/active_directory.js @@ -0,0 +1,58 @@ +// getDomainControllerName returns the domain controller +// name for a host. +// +// If the name is not empty, it is returned. +// Otherwise, the host is used to query the domain controller name. +// from SMB, LDAP, etc +function getDomainControllerName(name, host) { + if (name != "") { + return name; + } + + // First try LDAP and then SMB + try { + var name = getDomainControllerNameByLDAP(host); + if (name != "") { + return name; + } + } catch (e) { + console.log("[ldap] Error getting domain controller name", e); + } + + try { + var name = getDomainControllerNameBySMB(host); + if (name != "") { + return name; + } + } catch (e) { + console.log("[smb] Error getting domain controller name", e); + } + + return ""; +} + +function getDomainControllerNameBySMB(host) { + const s = require("nuclei/libsmb"); + const sc = s.Client(); + var list = sc.ListSMBv2Metadata(host, 445); + if (!list) { + return ""; + } + if (list && list.DNSDomainName != "") { + console.log("[smb] Got domain controller", list.DNSDomainName); + return list.DNSDomainName; + } +} + +function getDomainControllerNameByLDAP(host) { + const l = require("nuclei/libldap"); + const lc = l.Client(); + var list = lc.CollectLdapMetadata("", host); + if (!list) { + return ""; + } + if (list && list.domain != "") { + console.log("[ldap] Got domain controller", list.domain); + return list.domain; + } +} diff --git a/v2/pkg/js/global/js/dump.js b/v2/pkg/js/global/js/dump.js new file mode 100644 index 00000000..141f2867 --- /dev/null +++ b/v2/pkg/js/global/js/dump.js @@ -0,0 +1,25 @@ +// dump_json dumps the data as JSON to the console. +// It returns beautified JSON. +function dump_json(data) { + console.log(JSON.stringify(data, null, 2)); +} + +// to_json returns beautified JSON. +function to_json(data) { + return JSON.stringify(data, null, 2); +} + +// to_array sets object type as array +function to_array(data) { + return Object.setPrototypeOf(data, Array.prototype); +} + +// hex_to_ascii converts a hex string to ascii. +function hex_to_ascii(str1) { + var hex = str1.toString(); + var str = ""; + for (var n = 0; n < hex.length; n += 2) { + str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); + } + return str; +} diff --git a/v2/pkg/js/global/scripts.go b/v2/pkg/js/global/scripts.go new file mode 100644 index 00000000..6b4d835a --- /dev/null +++ b/v2/pkg/js/global/scripts.go @@ -0,0 +1,216 @@ +package global + +import ( + "bytes" + "embed" + "math/rand" + "net" + "reflect" + "time" + + "github.com/dop251/goja" + "github.com/logrusorgru/aurora" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + errorutil "github.com/projectdiscovery/utils/errors" + stringsutil "github.com/projectdiscovery/utils/strings" +) + +var ( + //go:embed js + embedFS embed.FS + + //go:embed exports.js + exports string + // knownPorts is a list of known ports for protocols implemented in nuclei + knowPorts = []string{"80", "443", "8080", "8081", "8443", "53"} +) + +// default imported modules +// there might be other methods to achieve this +// but this is most straightforward +var ( + defaultImports = ` + var structs = require("nuclei/structs"); + var bytes = require("nuclei/bytes"); + ` +) + +// initBuiltInFunc initializes runtime with builtin functions +func initBuiltInFunc(runtime *goja.Runtime) { + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "Rand", + Signatures: []string{"Rand(n int) []byte"}, + Description: "Rand returns a random byte slice of length n", + FuncDecl: func(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(rand.Intn(255)) + } + return b + }, + }) + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "RandInt", + Signatures: []string{"RandInt() int"}, + Description: "RandInt returns a random int", + FuncDecl: func() int64 { + return rand.Int63() + }, + }) + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "log", + Signatures: []string{ + "log(msg string)", + "log(msg map[string]interface{})", + }, + Description: "log prints given input to stdout with [JS] prefix for debugging purposes ", + FuncDecl: func(call goja.FunctionCall) goja.Value { + arg := call.Argument(0).Export() + switch value := arg.(type) { + case string: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value) + case map[string]interface{}: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), vardump.DumpVariables(value)) + default: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value) + } + return goja.Null() + }, + }) + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "getNetworkPort", + Signatures: []string{ + "getNetworkPort(port string, defaultPort string) string", + }, + Description: "getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols", + FuncDecl: func(call goja.FunctionCall) goja.Value { + inputPort := call.Argument(0).String() + if inputPort == "" || stringsutil.EqualFoldAny(inputPort, knowPorts...) { + // if inputPort is empty or a know port of other protocol + // return given defaultPort + return call.Argument(1) + } + return call.Argument(0) + }, + }) + + // is port open check is port is actually open + // it can be invoked as isPortOpen(host, port, [timeout]) + // where timeout is optional and defaults to 5 seconds + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "isPortOpen", + Signatures: []string{ + "isPortOpen(host string, port string, [timeout int]) bool", + }, + Description: "isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds", + FuncDecl: func(host string, port string, timeout ...int) (bool, error) { + timeoutInSec := 5 + if len(timeout) > 0 { + timeoutInSec = timeout[0] + } + conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), time.Duration(timeoutInSec)*time.Second) + if err != nil { + return false, err + } + _ = conn.Close() + return true, nil + }, + }) + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "ToBytes", + Signatures: []string{ + "ToBytes(...interface{}) []byte", + }, + Description: "ToBytes converts given input to byte slice", + FuncDecl: func(call goja.FunctionCall) goja.Value { + var buff bytes.Buffer + allVars := []any{} + for _, v := range call.Arguments { + if v.Export() == nil { + continue + } + if v.ExportType().Kind() == reflect.Slice { + // convert []datatype to []interface{} + // since it cannot be type asserted to []interface{} directly + rfValue := reflect.ValueOf(v.Export()) + for i := 0; i < rfValue.Len(); i++ { + allVars = append(allVars, rfValue.Index(i).Interface()) + } + } else { + allVars = append(allVars, v.Export()) + } + } + for _, v := range allVars { + buff.WriteString(types.ToString(v)) + } + return runtime.ToValue(buff.Bytes()) + }, + }) + + _ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "ToString", + Signatures: []string{ + "ToString(...interface{}) string", + }, + Description: "ToString converts given input to string", + FuncDecl: func(call goja.FunctionCall) goja.Value { + var buff bytes.Buffer + for _, v := range call.Arguments { + exported := v.Export() + if exported != nil { + buff.WriteString(types.ToString(exported)) + } + } + return runtime.ToValue(buff.String()) + }, + }) +} + +// RegisterNativeScripts are js scripts that were added for convenience +// and abstraction purposes we execute them in every runtime and make them +// available for use in any js script +// see: scripts/ for examples +func RegisterNativeScripts(runtime *goja.Runtime) error { + initBuiltInFunc(runtime) + + dirs, err := embedFS.ReadDir("js") + if err != nil { + return err + } + for _, dir := range dirs { + if dir.IsDir() { + continue + } + // embeds have / as path separator (on all os) + contents, err := embedFS.ReadFile("js" + "/" + dir.Name()) + if err != nil { + return err + } + // run all built in js helper functions or scripts + _, err = runtime.RunString(string(contents)) + if err != nil { + return err + } + } + // exports defines the exports object + _, err = runtime.RunString(exports) + if err != nil { + return err + } + + // import default modules + _, err = runtime.RunString(defaultImports) + if err != nil { + return errorutil.NewWithErr(err).Msgf("could not import default modules %v", defaultImports) + } + + return nil +} diff --git a/v2/pkg/js/global/scripts_test.go b/v2/pkg/js/global/scripts_test.go new file mode 100644 index 00000000..4105695f --- /dev/null +++ b/v2/pkg/js/global/scripts_test.go @@ -0,0 +1,28 @@ +package global + +import ( + "testing" + + "github.com/dop251/goja" + "github.com/dop251/goja_nodejs/console" + "github.com/dop251/goja_nodejs/require" +) + +func TestScriptsRuntime(t *testing.T) { + defaultImports = "" + runtime := goja.New() + + registry := new(require.Registry) + registry.Enable(runtime) + console.Enable(runtime) + + err := RegisterNativeScripts(runtime) + if err != nil { + t.Fatal(err) + } + value, err := runtime.RunString("dump_json({a: 1, b: 2})") + if err != nil { + t.Fatal(err) + } + _ = value +} diff --git a/v2/pkg/js/gojs/gojs.go b/v2/pkg/js/gojs/gojs.go new file mode 100644 index 00000000..0b47cbd1 --- /dev/null +++ b/v2/pkg/js/gojs/gojs.go @@ -0,0 +1,77 @@ +package gojs + +import ( + "sync" + + "github.com/dop251/goja" + "github.com/dop251/goja_nodejs/require" +) + +type Objects map[string]interface{} + +type Runtime interface { + Set(string, interface{}) error +} + +type Object interface { + Set(string, interface{}) + Get(string) interface{} +} + +type Module interface { + Name() string + Set(objects Objects) Module + Enable(Runtime) + Register() Module +} + +type GojaModule struct { + name string + sets map[string]interface{} + once sync.Once +} + +func NewGojaModule(name string) Module { + return &GojaModule{ + name: name, + sets: make(map[string]interface{}), + } +} + +func (p *GojaModule) String() string { + return p.name +} + +func (p *GojaModule) Name() string { + return p.name +} + +func (p *GojaModule) Set(objects Objects) Module { + + for k, v := range objects { + p.sets[k] = v + } + + return p +} + +func (p *GojaModule) Require(runtime *goja.Runtime, module *goja.Object) { + + o := module.Get("exports").(*goja.Object) + + for k, v := range p.sets { + _ = o.Set(k, v) + } +} + +func (p *GojaModule) Enable(runtime Runtime) { + _ = runtime.Set(p.Name(), require.Require(runtime.(*goja.Runtime), p.Name())) +} + +func (p *GojaModule) Register() Module { + p.once.Do(func() { + require.RegisterNativeModule(p.Name(), p.Require) + }) + + return p +} diff --git a/v2/pkg/js/gojs/set.go b/v2/pkg/js/gojs/set.go new file mode 100644 index 00000000..38e45d8d --- /dev/null +++ b/v2/pkg/js/gojs/set.go @@ -0,0 +1,30 @@ +package gojs + +import ( + "github.com/dop251/goja" + errorutil "github.com/projectdiscovery/utils/errors" +) + +var ( + ErrInvalidFuncOpts = errorutil.NewWithFmt("invalid function options: %v") +) + +type FuncOpts struct { + Name string + Signatures []string + Description string + FuncDecl interface{} +} + +// valid checks if the function options are valid +func (f *FuncOpts) valid() bool { + return f.Name != "" && f.FuncDecl != nil && len(f.Signatures) > 0 && f.Description != "" +} + +// RegisterFunc registers a function with given name, signatures and description +func RegisterFuncWithSignature(runtime *goja.Runtime, opts FuncOpts) error { + if !opts.valid() { + return ErrInvalidFuncOpts.Msgf("name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description) + } + return runtime.Set(opts.Name, opts.FuncDecl) +} diff --git a/v2/pkg/js/libs/LICENSE.md b/v2/pkg/js/libs/LICENSE.md new file mode 100644 index 00000000..9a578753 --- /dev/null +++ b/v2/pkg/js/libs/LICENSE.md @@ -0,0 +1,203 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +This project uses modules from praetorian/fingerprintx for detection of network protocols which is licensed under Apache 2.0. \ No newline at end of file diff --git a/v2/pkg/js/libs/bytes/buffer.go b/v2/pkg/js/libs/bytes/buffer.go new file mode 100644 index 00000000..ab3b75eb --- /dev/null +++ b/v2/pkg/js/libs/bytes/buffer.go @@ -0,0 +1,78 @@ +package bytes + +import ( + "encoding/hex" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/structs" +) + +// Buffer is a minimal buffer implementation over a byte slice +// that is used to pack/unpack binary data in nuclei js integration. +type Buffer struct { + buf []byte +} + +// NewBuffer creates a new buffer from a byte slice. +func NewBuffer(call goja.ConstructorCall) interface{} { + obj := &Buffer{} + + obj.buf = make([]byte, 0) + return map[string]interface{}{ + "Write": obj.Write, + "Pack": obj.Pack, + "Bytes": obj.Bytes, + "String": obj.String, + "Len": obj.Len, + "Hex": obj.Hex, + "Hexdump": obj.Hexdump, + } +} + +// Write appends a byte slice to the buffer. +func (b *Buffer) Write(data []byte) *Buffer { + b.buf = append(b.buf, data...) + return b +} + +// WriteString appends a string to the buffer. +func (b *Buffer) WriteString(data string) *Buffer { + b.buf = append(b.buf, []byte(data)...) + return b +} + +// Bytes returns the byte slice of the buffer. +func (b *Buffer) Bytes() []byte { + return b.buf +} + +// String returns the string representation of the buffer. +func (b *Buffer) String() string { + return string(b.buf) +} + +// Len returns the length of the buffer. +func (b *Buffer) Len() int { + return len(b.buf) +} + +// Hex returns the hex representation of the buffer. +func (b *Buffer) Hex() string { + return hex.EncodeToString(b.buf) +} + +// Hexdump returns the hexdump representation of the buffer. +func (b *Buffer) Hexdump() string { + return hex.Dump(b.buf) +} + +// Pack uses structs.Pack and packs given data and appends it to the buffer. +// it packs the data according to the given format. +func (b *Buffer) Pack(formatStr string, msg []interface{}) error { + bin, err := structs.Pack(formatStr, msg) + if err != nil { + return err + } + b.Write(bin) + return nil +} diff --git a/v2/pkg/js/libs/fs/fs.go b/v2/pkg/js/libs/fs/fs.go new file mode 100644 index 00000000..5421f652 --- /dev/null +++ b/v2/pkg/js/libs/fs/fs.go @@ -0,0 +1,70 @@ +package fs + +import ( + "os" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// ListDir lists all files and directories within a path +// depending on the itemType provided +// itemType can be any one of ['file','dir','all'] +func ListDir(path string, itemType string) ([]string, error) { + finalPath, err := protocolstate.NormalizePath(path) + if err != nil { + return nil, err + } + values, err := os.ReadDir(finalPath) + if err != nil { + return nil, err + } + var results []string + for _, value := range values { + if itemType == "file" && value.IsDir() { + continue + } + if itemType == "dir" && !value.IsDir() { + continue + } + results = append(results, value.Name()) + } + return results, nil +} + +// ReadFile reads file contents within permitted paths +func ReadFile(path string) ([]byte, error) { + finalPath, err := protocolstate.NormalizePath(path) + if err != nil { + return nil, err + } + bin, err := os.ReadFile(finalPath) + return bin, err +} + +// ReadFileAsString reads file contents within permitted paths +// and returns content as string +func ReadFileAsString(path string) (string, error) { + bin, err := ReadFile(path) + if err != nil { + return "", err + } + return string(bin), nil +} + +// ReadFilesFromDir reads all files from a directory +// and returns a array with file contents of all files +func ReadFilesFromDir(dir string) ([]string, error) { + files, err := ListDir(dir, "file") + if err != nil { + return nil, err + } + var results []string + for _, file := range files { + content, err := ReadFileAsString(dir + "/" + file) + if err != nil { + return nil, err + } + results = append(results, content) + } + return results, nil +} diff --git a/v2/pkg/js/libs/goconsole/log.go b/v2/pkg/js/libs/goconsole/log.go new file mode 100644 index 00000000..994d6609 --- /dev/null +++ b/v2/pkg/js/libs/goconsole/log.go @@ -0,0 +1,31 @@ +package goconsole + +import ( + "github.com/dop251/goja_nodejs/console" + "github.com/projectdiscovery/gologger" +) + +var _ console.Printer = &GoConsolePrinter{} + +// GoConsolePrinter is a console printer for nuclei using gologger +type GoConsolePrinter struct { + logger *gologger.Logger +} + +func NewGoConsolePrinter() *GoConsolePrinter { + return &GoConsolePrinter{ + logger: gologger.DefaultLogger, + } +} + +func (p *GoConsolePrinter) Log(msg string) { + p.logger.Info().Msg(msg) +} + +func (p *GoConsolePrinter) Warn(msg string) { + p.logger.Warning().Msg(msg) +} + +func (p *GoConsolePrinter) Error(msg string) { + p.logger.Error().Msg(msg) +} diff --git a/v2/pkg/js/libs/ikev2/ikev2.go b/v2/pkg/js/libs/ikev2/ikev2.go new file mode 100644 index 00000000..c41fedf2 --- /dev/null +++ b/v2/pkg/js/libs/ikev2/ikev2.go @@ -0,0 +1,104 @@ +package ikev2 + +import ( + "io" + + "github.com/projectdiscovery/n3iwf/pkg/ike/message" + "github.com/projectdiscovery/n3iwf/pkg/logger" +) + +func init() { + logger.Log.SetOutput(io.Discard) +} + +// IKEMessage is the IKEv2 message +// +// IKEv2 implements a limited subset of IKEv2 Protocol, specifically +// the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange. +type IKEMessage struct { + InitiatorSPI uint64 + Version uint8 + ExchangeType uint8 + Flags uint8 + Payloads []IKEPayload +} + +// IKEPayload is the IKEv2 payload interface +// +// All the payloads like IKENotification, IKENonce, etc. implement +// this interface. +type IKEPayload interface { + encode() (message.IKEPayload, error) +} + +// IKEv2Notify is the IKEv2 Notification payload +type IKENotification struct { + NotifyMessageType uint16 + NotificationData []byte +} + +// encode encodes the IKEv2 Notification payload +func (i *IKENotification) encode() (message.IKEPayload, error) { + notify := message.Notification{ + NotifyMessageType: i.NotifyMessageType, + NotificationData: i.NotificationData, + } + return ¬ify, nil +} + +const ( + // Notify message types + IKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14 + IKE_NOTIFY_USE_TRANSPORT_MODE = 16391 + + IKE_VERSION_2 = 0x20 + + // Exchange Type + IKE_EXCHANGE_SA_INIT = 34 + IKE_EXCHANGE_AUTH = 35 + IKE_EXCHANGE_CREATE_CHILD_SA = 36 + IKE_EXCHANGE_INFORMATIONAL = 37 + + // Flags + IKE_FLAGS_InitiatorBitCheck = 0x08 +) + +// IKENonce is the IKEv2 Nonce payload +type IKENonce struct { + NonceData []byte +} + +// encode encodes the IKEv2 Nonce payload +func (i *IKENonce) encode() (message.IKEPayload, error) { + nonce := message.Nonce{ + NonceData: i.NonceData, + } + return &nonce, nil +} + +// AppendPayload appends a payload to the IKE message +func (m *IKEMessage) AppendPayload(payload IKEPayload) { + m.Payloads = append(m.Payloads, payload) +} + +// Encode encodes the final IKE message +func (m *IKEMessage) Encode() ([]byte, error) { + var payloads message.IKEPayloadContainer + for _, payload := range m.Payloads { + p, err := payload.encode() + if err != nil { + return nil, err + } + payloads = append(payloads, p) + } + + msg := &message.IKEMessage{ + InitiatorSPI: m.InitiatorSPI, + Version: m.Version, + ExchangeType: m.ExchangeType, + Flags: m.Flags, + Payloads: payloads, + } + encoded, err := msg.Encode() + return encoded, err +} diff --git a/v2/pkg/js/libs/kerberos/kerberos.go b/v2/pkg/js/libs/kerberos/kerberos.go new file mode 100644 index 00000000..ac5708a5 --- /dev/null +++ b/v2/pkg/js/libs/kerberos/kerberos.go @@ -0,0 +1,145 @@ +package kerberos + +import ( + "encoding/hex" + "fmt" + "html/template" + "strings" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + kclient "github.com/ropnop/gokrb5/v8/client" + kconfig "github.com/ropnop/gokrb5/v8/config" + "github.com/ropnop/gokrb5/v8/iana/errorcode" + "github.com/ropnop/gokrb5/v8/messages" +) + +// Client is a kerberos client +type KerberosClient struct{} + +type kerberosEnumUserOpts struct { + realm string + config *kconfig.Config + kdcs map[int]string +} + +// Taken from kerbrute: https://github.com/ropnop/kerbrute/blob/master/session/session.go + +const krb5ConfigTemplateDNS = `[libdefaults] +dns_lookup_kdc = true +default_realm = {{.Realm}} +` + +const krb5ConfigTemplateKDC = `[libdefaults] +default_realm = {{.Realm}} +[realms] +{{.Realm}} = { + kdc = {{.DomainController}} + admin_server = {{.DomainController}} +} +` + +func buildKrb5Template(realm, domainController string) string { + data := map[string]interface{}{ + "Realm": realm, + "DomainController": domainController, + } + var kTemplate string + if domainController == "" { + kTemplate = krb5ConfigTemplateDNS + } else { + kTemplate = krb5ConfigTemplateKDC + } + t := template.Must(template.New("krb5ConfigString").Parse(kTemplate)) + builder := &strings.Builder{} + if err := t.Execute(builder, data); err != nil { + panic(err) + } + return builder.String() +} + +func newKerbrosEnumUserOpts(domain, domainController string) (*kerberosEnumUserOpts, error) { + realm := strings.ToUpper(domain) + configstring := buildKrb5Template(realm, domainController) + Config, err := kconfig.NewFromString(configstring) + if err != nil { + return nil, err + } + _, kdcs, err := Config.GetKDCs(realm, false) + if err != nil { + err = fmt.Errorf("couldn't find any KDCs for realm %s. Please specify a Domain Controller", realm) + return nil, err + } + return &kerberosEnumUserOpts{realm: realm, config: Config, kdcs: kdcs}, nil +} + +// EnumerateUserResponse is the response from EnumerateUser +type EnumerateUserResponse struct { + Valid bool + ASREPHash string +} + +// EnumerateUser returns true if the user exists in the domain +// +// If the user is not found, false is returned. +// If the user is found, true is returned. Optionally, the AS-REP +// hash is also returned if discovered. +func (c *KerberosClient) EnumerateUser(domain, controller string, username string) (EnumerateUserResponse, error) { + + resp := EnumerateUserResponse{} + + if !protocolstate.IsHostAllowed(domain) { + // host is not valid according to network policy + return resp, protocolstate.ErrHostDenied.Msgf(domain) + } + + opts, err := newKerbrosEnumUserOpts(domain, controller) + if err != nil { + return resp, err + } + cl := kclient.NewWithPassword(username, opts.realm, "foobar", opts.config, kclient.DisablePAFXFAST(true)) + + req, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName()) + if err != nil { + return resp, err + } + b, err := req.Marshal() + if err != nil { + return resp, err + } + rb, err := cl.SendToKDC(b, opts.realm) + if err == nil { + var ASRep messages.ASRep + err = ASRep.Unmarshal(rb) + if err != nil { + // something went wrong, it's not a valid response + return resp, err + } + hashcatString, _ := asRepToHashcat(ASRep) + resp.Valid = true + resp.ASREPHash = hashcatString + return resp, nil + } + e, ok := err.(messages.KRBError) + if !ok { + return resp, nil + } + switch e.ErrorCode { + case errorcode.KDC_ERR_C_PRINCIPAL_UNKNOWN: + return resp, nil + case errorcode.KDC_ERR_PREAUTH_REQUIRED: + resp.Valid = true + return resp, nil + default: + return resp, err + + } +} + +func asRepToHashcat(asrep messages.ASRep) (string, error) { + return fmt.Sprintf("$krb5asrep$%d$%s@%s:%s$%s", + asrep.EncPart.EType, + asrep.CName.PrincipalNameString(), + asrep.CRealm, + hex.EncodeToString(asrep.EncPart.Cipher[:16]), + hex.EncodeToString(asrep.EncPart.Cipher[16:])), nil +} diff --git a/v2/pkg/js/libs/ldap/ldap.go b/v2/pkg/js/libs/ldap/ldap.go new file mode 100644 index 00000000..d6ad3397 --- /dev/null +++ b/v2/pkg/js/libs/ldap/ldap.go @@ -0,0 +1,205 @@ +package ldap + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/go-ldap/ldap/v3" + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + + pluginldap "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/ldap" +) + +// Client is a client for ldap protocol in golang. +// +// It is a wrapper around the standard library ldap package. +type LdapClient struct{} + +// IsLdap checks if the given host and port are running ldap server. +func (c *LdapClient) IsLdap(host string, port int) (bool, error) { + + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + timeout := 10 * time.Second + + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + + if err != nil { + return false, err + } + defer conn.Close() + + _ = conn.SetDeadline(time.Now().Add(timeout)) + + plugin := &pluginldap.LDAPPlugin{} + service, err := plugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return false, err + } + if service == nil { + return false, nil + } + return true, nil +} + +// CollectLdapMetadata collects metadata from ldap server. +func (c *LdapClient) CollectLdapMetadata(domain string, controller string) (LDAPMetadata, error) { + opts := &ldapSessionOptions{ + domain: domain, + domainController: controller, + } + + if !protocolstate.IsHostAllowed(domain) { + // host is not valid according to network policy + return LDAPMetadata{}, protocolstate.ErrHostDenied.Msgf(domain) + } + + conn, err := c.newLdapSession(opts) + if err != nil { + return LDAPMetadata{}, err + } + defer c.close(conn) + + return c.collectLdapMetadata(conn, opts) +} + +type ldapSessionOptions struct { + domain string + domainController string + port int + username string + password string + baseDN string +} + +func (c *LdapClient) newLdapSession(opts *ldapSessionOptions) (*ldap.Conn, error) { + port := opts.port + dc := opts.domainController + if port == 0 { + port = 389 + } + + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", dc, port)) + if err != nil { + return nil, err + } + + lConn := ldap.NewConn(conn, false) + lConn.Start() + + return lConn, nil +} + +func (c *LdapClient) close(conn *ldap.Conn) { + conn.Close() +} + +// LDAPMetadata is the metadata for ldap server. +type LDAPMetadata struct { + BaseDN string + Domain string + DefaultNamingContext string + DomainFunctionality string + ForestFunctionality string + DomainControllerFunctionality string + DnsHostName string +} + +func (c *LdapClient) collectLdapMetadata(lConn *ldap.Conn, opts *ldapSessionOptions) (LDAPMetadata, error) { + metadata := LDAPMetadata{} + + var err error + if opts.username == "" { + err = lConn.UnauthenticatedBind("") + } else { + err = lConn.Bind(opts.username, opts.password) + } + if err != nil { + return metadata, err + } + + baseDN, _ := getBaseNamingContext(opts, lConn) + + metadata.BaseDN = baseDN + metadata.Domain = parseDC(baseDN) + + srMetadata := ldap.NewSearchRequest( + "", + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{ + "defaultNamingContext", + "domainFunctionality", + "forestFunctionality", + "domainControllerFunctionality", + "dnsHostName", + }, + nil) + resMetadata, err := lConn.Search(srMetadata) + if err != nil { + return metadata, err + } + for _, entry := range resMetadata.Entries { + for _, attr := range entry.Attributes { + value := entry.GetAttributeValue(attr.Name) + switch attr.Name { + case "defaultNamingContext": + metadata.DefaultNamingContext = value + case "domainFunctionality": + metadata.DomainFunctionality = value + case "forestFunctionality": + metadata.ForestFunctionality = value + case "domainControllerFunctionality": + metadata.DomainControllerFunctionality = value + case "dnsHostName": + metadata.DnsHostName = value + } + } + } + return metadata, nil +} + +func parseDC(input string) string { + parts := strings.Split(strings.ToLower(input), ",") + + for i, part := range parts { + parts[i] = strings.TrimPrefix(part, "dc=") + } + + return strings.Join(parts, ".") +} + +func getBaseNamingContext(opts *ldapSessionOptions, conn *ldap.Conn) (string, error) { + if opts.baseDN != "" { + return opts.baseDN, nil + } + sr := ldap.NewSearchRequest( + "", + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"defaultNamingContext"}, + nil) + res, err := conn.Search(sr) + if err != nil { + return "", err + } + if len(res.Entries) == 0 { + return "", fmt.Errorf("error getting metadata: No LDAP responses from server") + } + defaultNamingContext := res.Entries[0].GetAttributeValue("defaultNamingContext") + if defaultNamingContext == "" { + return "", fmt.Errorf("error getting metadata: attribute defaultNamingContext missing") + } + opts.baseDN = defaultNamingContext + return opts.baseDN, nil +} diff --git a/v2/pkg/js/libs/mssql/mssql.go b/v2/pkg/js/libs/mssql/mssql.go new file mode 100644 index 00000000..4855791c --- /dev/null +++ b/v2/pkg/js/libs/mssql/mssql.go @@ -0,0 +1,110 @@ +package mssql + +import ( + "context" + "database/sql" + "fmt" + "net" + "net/url" + "strings" + "time" + + _ "github.com/denisenkom/go-mssqldb" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mssql" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// Client is a client for MS SQL database. +// +// Internally client uses denisenkom/go-mssqldb driver. +type MSSQLClient struct{} + +// Connect connects to MS SQL database using given credentials. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *MSSQLClient) Connect(host string, port int, username, password string) (bool, error) { + return connect(host, port, username, password, "master") +} + +// ConnectWithDB connects to MS SQL database using given credentials and database name. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *MSSQLClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) { + return connect(host, port, username, password, dbName) +} + +func connect(host string, port int, username, password, dbName string) (bool, error) { + if host == "" || port <= 0 { + return false, fmt.Errorf("invalid host or port") + } + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + connString := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s&connection+timeout=30", + url.PathEscape(username), + url.PathEscape(password), + target, + dbName) + + db, err := sql.Open("sqlserver", connString) + if err != nil { + return false, err + } + defer db.Close() + + _, err = db.Exec("select 1") + if err != nil { + switch { + case strings.Contains(err.Error(), "connect: connection refused"): + fallthrough + case strings.Contains(err.Error(), "no pg_hba.conf entry for host"): + fallthrough + case strings.Contains(err.Error(), "network unreachable"): + fallthrough + case strings.Contains(err.Error(), "reset"): + fallthrough + case strings.Contains(err.Error(), "i/o timeout"): + return false, err + } + return false, nil + } + return true, nil +} + +// IsMssql checks if the given host is running MS SQL database. +// +// If the host is running MS SQL database, it returns true. +// If the host is not running MS SQL database, it returns false. +func (c *MSSQLClient) IsMssql(host string, port int) (bool, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + if err != nil { + return false, err + } + defer conn.Close() + + data, check, err := mssql.DetectMSSQL(conn, 5*time.Second) + if check && err != nil { + return false, nil + } else if !check && err != nil { + return false, err + } + if data.Version != "" { + return true, nil + } + return false, nil +} diff --git a/v2/pkg/js/libs/mysql/mysql.go b/v2/pkg/js/libs/mysql/mysql.go new file mode 100644 index 00000000..6be976ab --- /dev/null +++ b/v2/pkg/js/libs/mysql/mysql.go @@ -0,0 +1,128 @@ +package mysql + +import ( + "context" + "database/sql" + "fmt" + "net" + "net/url" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + mysqlplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mysql" + utils "github.com/projectdiscovery/nuclei/v2/pkg/js/utils" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// MySQLClient is a client for MySQL database. +// +// Internally client uses go-sql-driver/mysql driver. +type MySQLClient struct{} + +// Connect connects to MySQL database using given credentials. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *MySQLClient) Connect(host string, port int, username, password string) (bool, error) { + return connect(host, port, username, password, "INFORMATION_SCHEMA") +} + +// IsMySQL checks if the given host is running MySQL database. +// +// If the host is running MySQL database, it returns true. +// If the host is not running MySQL database, it returns false. +func (c *MySQLClient) IsMySQL(host string, port int) (bool, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + if err != nil { + return false, err + } + defer conn.Close() + + plugin := &mysqlplugin.MYSQLPlugin{} + service, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host}) + if err != nil { + return false, err + } + if service == nil { + return false, nil + } + return true, nil +} + +// ConnectWithDB connects to MySQL database using given credentials and database name. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *MySQLClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) { + return connect(host, port, username, password, dbName) +} + +func connect(host string, port int, username, password, dbName string) (bool, error) { + if host == "" || port <= 0 { + return false, fmt.Errorf("invalid host or port") + } + + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + db, err := sql.Open("mysql", fmt.Sprintf("%v:%v@tcp(%v)/%s", + url.PathEscape(username), + url.PathEscape(password), + target, + dbName)) + if err != nil { + return false, err + } + defer db.Close() + + _, err = db.Exec("select 1") + if err != nil { + return false, err + } + return true, nil +} + +// ExecuteQuery connects to Mysql database using given credentials and database name. +// and executes a query on the db. +func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, dbName, query string) (string, error) { + + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return "", protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + db, err := sql.Open("mysql", fmt.Sprintf("%v:%v@tcp(%v)/%s", + url.PathEscape(username), + url.PathEscape(password), + target, + dbName)) + if err != nil { + return "", err + } + defer db.Close() + + rows, err := db.Query(query) + if err != nil { + return "", err + } + resp, err := utils.UnmarshalSQLRows(rows) + if err != nil { + return "", err + } + return string(resp), nil +} diff --git a/v2/pkg/js/libs/net/net.go b/v2/pkg/js/libs/net/net.go new file mode 100644 index 00000000..0ae05a6d --- /dev/null +++ b/v2/pkg/js/libs/net/net.go @@ -0,0 +1,157 @@ +package net + +import ( + "context" + "crypto/tls" + "encoding/hex" + "errors" + "fmt" + "net" + "syscall" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/nuclei/v2/pkg/types" +) + +// Open opens a new connection to the address with a timeout. +// supported protocols: tcp, udp +func Open(protocol, address string) (*NetConn, error) { + conn, err := protocolstate.Dialer.Dial(context.TODO(), protocol, address) + if err != nil { + return nil, err + } + return &NetConn{conn: conn}, nil +} + +// Open opens a new connection to the address with a timeout. +// supported protocols: tcp, udp +func OpenTLS(protocol, address string) (*NetConn, error) { + config := &tls.Config{InsecureSkipVerify: true} + host, _, _ := net.SplitHostPort(address) + if host != "" { + c := config.Clone() + c.ServerName = host + config = c + } + conn, err := protocolstate.Dialer.DialTLSWithConfig(context.TODO(), protocol, address, config) + if err != nil { + return nil, err + } + return &NetConn{conn: conn}, nil +} + +// NetConn is a connection to a remote host. +type NetConn struct { + conn net.Conn + timeout time.Duration +} + +// Close closes the connection. +func (c *NetConn) Close() error { + err := c.conn.Close() + return err +} + +// SetTimeout sets read/write timeout for the connection (in seconds). +func (c *NetConn) SetTimeout(value int) { + c.timeout = time.Duration(value) * time.Second +} + +// setDeadLine sets read/write deadline for the connection (in seconds). +// this is intended to be called before every read/write operation. +func (c *NetConn) setDeadLine() { + if c.timeout == 0 { + c.timeout = 5 * time.Second + } + _ = c.conn.SetDeadline(time.Now().Add(c.timeout)) +} + +// SendArray sends array data to connection +func (c *NetConn) SendArray(data []interface{}) error { + c.setDeadLine() + input := types.ToByteSlice(data) + length, err := c.conn.Write(input) + if err != nil { + return err + } + if length < len(input) { + return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(input)) + } + return nil +} + +// SendHex sends hex data to connection +func (c *NetConn) SendHex(data string) error { + c.setDeadLine() + bin, err := hex.DecodeString(data) + if err != nil { + return err + } + length, err := c.conn.Write(bin) + if err != nil { + return err + } + if length < len(bin) { + return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(bin)) + } + return nil +} + +// Send sends data to the connection with a timeout. +func (c *NetConn) Send(data string) error { + c.setDeadLine() + bin := []byte(data) + length, err := c.conn.Write(bin) + if err != nil { + return err + } + if length < len(bin) { + return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(data)) + } + return nil +} + +// Recv receives data from the connection with a timeout. +// If N is 0, it will read up to 4096 bytes. +func (c *NetConn) Recv(N int) ([]byte, error) { + c.setDeadLine() + var response []byte + if N > 0 { + response = make([]byte, N) + } else { + response = make([]byte, 4096) + } + length, err := c.conn.Read(response) + if err != nil { + var netErr net.Error + if (errors.As(err, &netErr) && netErr.Timeout()) || + errors.Is(err, syscall.ECONNREFUSED) { // timeout error or connection refused + return response, nil + } + return response[:length], err + } + return response[:length], nil +} + +// RecvString receives data from the connection with a timeout +// output is returned as a string. +// If N is 0, it will read up to 4096 bytes. +func (c *NetConn) RecvString(N int) (string, error) { + bin, err := c.Recv(N) + if err != nil { + return "", err + } + return string(bin), nil +} + +// RecvHex receives data from the connection with a timeout +// in hex format. +// If N is 0, it will read up to 4096 bytes. +func (c *NetConn) RecvHex(N int) (string, error) { + bin, err := c.Recv(N) + if err != nil { + return "", err + } + return hex.Dump(bin), nil +} diff --git a/v2/pkg/js/libs/oracle/oracle.go b/v2/pkg/js/libs/oracle/oracle.go new file mode 100644 index 00000000..9c5a4a01 --- /dev/null +++ b/v2/pkg/js/libs/oracle/oracle.go @@ -0,0 +1,46 @@ +package oracle + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// OracleClient is a minimal Oracle client for nuclei scripts. +type OracleClient struct{} + +// IsOracleResponse is the response from the IsOracle function. +type IsOracleResponse struct { + IsOracle bool + Banner string +} + +// IsOracle checks if a host is running an Oracle server. +func (c *OracleClient) IsOracle(host string, port int) (IsOracleResponse, error) { + resp := IsOracleResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + oracledbPlugin := oracledb.ORACLEPlugin{} + service, err := oracledbPlugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Version + resp.Banner = service.Metadata().(plugins.ServiceOracle).Info + resp.IsOracle = true + return resp, nil +} diff --git a/v2/pkg/js/libs/pop3/pop3.go b/v2/pkg/js/libs/pop3/pop3.go new file mode 100644 index 00000000..c98b6454 --- /dev/null +++ b/v2/pkg/js/libs/pop3/pop3.go @@ -0,0 +1,45 @@ +package pop3 + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/pop3" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// Pop3Client is a minimal POP3 client for nuclei scripts. +type Pop3Client struct{} + +// IsPOP3Response is the response from the IsPOP3 function. +type IsPOP3Response struct { + IsPOP3 bool + Banner string +} + +// IsPOP3 checks if a host is running a POP3 server. +func (c *Pop3Client) IsPOP3(host string, port int) (IsPOP3Response, error) { + resp := IsPOP3Response{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + pop3Plugin := pop3.POP3Plugin{} + service, err := pop3Plugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Metadata().(plugins.ServicePOP3).Banner + resp.IsPOP3 = true + return resp, nil +} diff --git a/v2/pkg/js/libs/postgres/postgres.go b/v2/pkg/js/libs/postgres/postgres.go new file mode 100644 index 00000000..81e79d6a --- /dev/null +++ b/v2/pkg/js/libs/postgres/postgres.go @@ -0,0 +1,133 @@ +package postgres + +import ( + "context" + "database/sql" + "fmt" + "net" + "strings" + "time" + + "github.com/go-pg/pg" + _ "github.com/lib/pq" + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + postgres "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/postgresql" + utils "github.com/projectdiscovery/nuclei/v2/pkg/js/utils" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// PGClient is a client for Postgres database. +// +// Internally client uses go-pg/pg driver. +type PGClient struct{} + +// IsPostgres checks if the given host and port are running Postgres database. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +func (c *PGClient) IsPostgres(host string, port int) (bool, error) { + timeout := 10 * time.Second + + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return false, err + } + defer conn.Close() + + _ = conn.SetDeadline(time.Now().Add(timeout)) + + plugin := &postgres.POSTGRESPlugin{} + service, err := plugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return false, err + } + if service == nil { + return false, nil + } + return true, nil +} + +// Connect connects to Postgres database using given credentials. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *PGClient) Connect(host string, port int, username, password string) (bool, error) { + return connect(host, port, username, password, "postgres") +} + +// ExecuteQuery connects to Postgres database using given credentials and database name. +// and executes a query on the db. +func (c *PGClient) ExecuteQuery(host string, port int, username, password, dbName, query string) (string, error) { + + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return "", protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", username, password, target, dbName) + db, err := sql.Open("postgres", connStr) + if err != nil { + return "", err + } + + rows, err := db.Query(query) + if err != nil { + return "", err + } + resp, err := utils.UnmarshalSQLRows(rows) + if err != nil { + return "", err + } + return string(resp), nil +} + +// ConnectWithDB connects to Postgres database using given credentials and database name. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The connection is closed after the function returns. +func (c *PGClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) { + return connect(host, port, username, password, dbName) +} + +func connect(host string, port int, username, password, dbName string) (bool, error) { + if host == "" || port <= 0 { + return false, fmt.Errorf("invalid host or port") + } + + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + db := pg.Connect(&pg.Options{ + Addr: target, + User: username, + Password: password, + Database: dbName, + }) + _, err := db.Exec("select 1") + if err != nil { + switch true { + case strings.Contains(err.Error(), "connect: connection refused"): + fallthrough + case strings.Contains(err.Error(), "no pg_hba.conf entry for host"): + fallthrough + case strings.Contains(err.Error(), "network unreachable"): + fallthrough + case strings.Contains(err.Error(), "reset"): + fallthrough + case strings.Contains(err.Error(), "i/o timeout"): + return false, err + } + return false, nil + } + return true, nil +} diff --git a/v2/pkg/js/libs/rdp/rdp.go b/v2/pkg/js/libs/rdp/rdp.go new file mode 100644 index 00000000..5221a374 --- /dev/null +++ b/v2/pkg/js/libs/rdp/rdp.go @@ -0,0 +1,76 @@ +package rdp + +import ( + "context" + "fmt" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rdp" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// RDPClient is a client for rdp servers +type RDPClient struct{} + +type IsRDPResponse struct { + IsRDP bool + OS string +} + +// IsRDP checks if the given host and port are running rdp server. +// +// If connection is successful, it returns true. +// If connection is unsuccessful, it returns false and error. +// +// The Name of the OS is also returned if the connection is successful. +func (c *RDPClient) IsRDP(host string, port int) (IsRDPResponse, error) { + resp := IsRDPResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return resp, err + } + defer conn.Close() + + server, isRDP, err := rdp.DetectRDP(conn, timeout) + if err != nil { + return resp, err + } + if !isRDP { + return resp, nil + } + resp.IsRDP = true + resp.OS = server + return resp, nil +} + +type CheckRDPAuthResponse struct { + PluginInfo *plugins.ServiceRDP + Auth bool +} + +// CheckRDPAuth checks if the given host and port are running rdp server +// with authentication and returns their metadata. +func (c *RDPClient) CheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { + resp := CheckRDPAuthResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return resp, err + } + defer conn.Close() + + pluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout) + if err != nil { + return resp, err + } + if !auth { + return resp, nil + } + resp.Auth = true + resp.PluginInfo = pluginInfo + return resp, nil +} diff --git a/v2/pkg/js/libs/redis/redis.go b/v2/pkg/js/libs/redis/redis.go new file mode 100644 index 00000000..29818cba --- /dev/null +++ b/v2/pkg/js/libs/redis/redis.go @@ -0,0 +1,140 @@ +package redis + +import ( + "context" + "fmt" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/redis/go-redis/v9" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + pluginsredis "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/redis" +) + +// GetServerInfo returns the server info for a redis server +func GetServerInfo(host string, port int) (string, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return "", protocolstate.ErrHostDenied.Msgf(host) + } + // create a new client + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Password: "", // no password set + DB: 0, // use default DB + }) + + // Ping the Redis server + _, err := client.Ping(context.TODO()).Result() + if err != nil { + return "", err + } + + // Get Redis server info + infoCmd := client.Info(context.TODO()) + if infoCmd.Err() != nil { + return "", infoCmd.Err() + } + + return infoCmd.Val(), nil +} + +// Connect tries to connect redis server with password +func Connect(host string, port int, password string) (bool, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + // create a new client + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Password: password, // no password set + DB: 0, // use default DB + }) + _, err := client.Ping(context.TODO()).Result() + if err != nil { + return false, err + } + // Get Redis server info + infoCmd := client.Info(context.TODO()) + if infoCmd.Err() != nil { + return false, infoCmd.Err() + } + + return true, nil +} + +// GetServerInfoAuth returns the server info for a redis server +func GetServerInfoAuth(host string, port int, password string) (string, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return "", protocolstate.ErrHostDenied.Msgf(host) + } + // create a new client + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Password: password, // no password set + DB: 0, // use default DB + }) + + // Ping the Redis server + _, err := client.Ping(context.TODO()).Result() + if err != nil { + return "", err + } + + // Get Redis server info + infoCmd := client.Info(context.TODO()) + if infoCmd.Err() != nil { + return "", infoCmd.Err() + } + + return infoCmd.Val(), nil +} + +// IsAuthenticated checks if the redis server requires authentication +func IsAuthenticated(host string, port int) (bool, error) { + plugin := pluginsredis.REDISPlugin{} + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return false, err + } + defer conn.Close() + + _, err = plugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return false, err + } + return true, nil +} + +// RunLuaScript runs a lua script on +func RunLuaScript(host string, port int, password string, script string) (interface{}, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + // create a new client + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Password: password, + DB: 0, // use default DB + }) + + // Ping the Redis server + _, err := client.Ping(context.TODO()).Result() + if err != nil { + return "", err + } + + // Get Redis server info + infoCmd := client.Eval(context.Background(), script, []string{}) + + if infoCmd.Err() != nil { + return "", infoCmd.Err() + } + + return infoCmd.Val(), nil +} diff --git a/v2/pkg/js/libs/rsync/rsync.go b/v2/pkg/js/libs/rsync/rsync.go new file mode 100644 index 00000000..4421138c --- /dev/null +++ b/v2/pkg/js/libs/rsync/rsync.go @@ -0,0 +1,45 @@ +package rsync + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rsync" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// RsyncClient is a minimal Rsync client for nuclei scripts. +type RsyncClient struct{} + +// IsRsyncResponse is the response from the IsRsync function. +type IsRsyncResponse struct { + IsRsync bool + Banner string +} + +// IsRsync checks if a host is running a Rsync server. +func (c *RsyncClient) IsRsync(host string, port int) (IsRsyncResponse, error) { + resp := IsRsyncResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + rsyncPlugin := rsync.RSYNCPlugin{} + service, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Version + resp.IsRsync = true + return resp, nil +} diff --git a/v2/pkg/js/libs/smb/metadata.go b/v2/pkg/js/libs/smb/metadata.go new file mode 100644 index 00000000..1b98a8e6 --- /dev/null +++ b/v2/pkg/js/libs/smb/metadata.go @@ -0,0 +1,30 @@ +package smb + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smb" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// collectSMBv2Metadata collects metadata for SMBv2 services. +func collectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) { + if timeout == 0 { + timeout = 5 * time.Second + } + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + if err != nil { + return nil, err + } + defer conn.Close() + + metadata, err := smb.DetectSMBv2(conn, timeout) + if err != nil { + return nil, err + } + return metadata, nil +} diff --git a/v2/pkg/js/libs/smb/smb.go b/v2/pkg/js/libs/smb/smb.go new file mode 100644 index 00000000..a524fc77 --- /dev/null +++ b/v2/pkg/js/libs/smb/smb.go @@ -0,0 +1,95 @@ +package smb + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/hirochachacha/go-smb2" + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/zmap/zgrab2/lib/smb/smb" +) + +// SMBClient is a client for SMB servers. +// +// Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver. +// github.com/hirochachacha/go-smb2 driver +type SMBClient struct{} + +// ConnectSMBInfoMode tries to connect to provided host and port +// and discovery SMB information +// +// Returns handshake log and error. If error is not nil, +// state will be false +func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return nil, err + } + defer conn.Close() + + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + setupSession := true + + result, err := smb.GetSMBLog(conn, setupSession, false, false) + if err != nil { + conn.Close() + conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 10*time.Second) + if err != nil { + return nil, err + } + result, err = smb.GetSMBLog(conn, setupSession, true, false) + if err != nil { + return nil, err + } + } + return result, nil +} + +// ListSMBv2Metadata tries to connect to provided host and port +// and list SMBv2 metadata. +// +// Returns metadata and error. If error is not nil, +// state will be false +func (c *SMBClient) ListSMBv2Metadata(host string, port int) (*plugins.ServiceSMB, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return nil, protocolstate.ErrHostDenied.Msgf(host) + } + return collectSMBv2Metadata(host, port, 5*time.Second) +} + +// ListShares tries to connect to provided host and port +// and list shares by using given credentials. +// +// Credentials cannot be blank. guest or anonymous credentials +// can be used by providing empty password. +func (c *SMBClient) ListShares(host string, port int, user, password string) ([]string, error) { + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return nil, err + } + defer conn.Close() + + d := &smb2.Dialer{ + Initiator: &smb2.NTLMInitiator{ + User: user, + Password: password, + }, + } + s, err := d.Dial(conn) + if err != nil { + return nil, err + } + defer func() { + _ = s.Logoff() + }() + + names, err := s.ListSharenames() + if err != nil { + return nil, err + } + return names, nil +} diff --git a/v2/pkg/js/libs/smb/smbghost.go b/v2/pkg/js/libs/smb/smbghost.go new file mode 100644 index 00000000..79c28b30 --- /dev/null +++ b/v2/pkg/js/libs/smb/smbghost.go @@ -0,0 +1,58 @@ +package smb + +import ( + "bytes" + "context" + "errors" + "net" + "strconv" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/js/libs/structs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +const ( + pkt = "\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02\"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" +) + +// DetectSMBGhost tries to detect SMBGhost vulnerability +// by using SMBv3 compression feature. +func (c *SMBClient) DetectSMBGhost(host string, port int) (bool, error) { + addr := net.JoinHostPort(host, strconv.Itoa(port)) + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", addr) + if err != nil { + return false, err + + } + defer conn.Close() + + _, err = conn.Write([]byte(pkt)) + if err != nil { + return false, err + } + + buff := make([]byte, 4) + nb, _ := conn.Read(buff) + args, err := structs.Unpack(">I", buff[:nb]) + if err != nil { + return false, err + } + if len(args) != 1 { + return false, errors.New("invalid response") + } + + length := args[0].(int) + data := make([]byte, length) + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + n, err := conn.Read(data) + if err != nil { + return false, err + } + data = data[:n] + + if !bytes.Equal(data[68:70], []byte("\x11\x03")) || !bytes.Equal(data[70:72], []byte("\x02\x00")) { + return false, nil + } + return true, nil +} diff --git a/v2/pkg/js/libs/smtp/smtp.go b/v2/pkg/js/libs/smtp/smtp.go new file mode 100644 index 00000000..c4f943f0 --- /dev/null +++ b/v2/pkg/js/libs/smtp/smtp.go @@ -0,0 +1,45 @@ +package smtp + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smtp" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// SMTPClient is a minimal SMTP client for nuclei scripts. +type SMTPClient struct{} + +// IsSMTPResponse is the response from the IsSMTP function. +type IsSMTPResponse struct { + IsSMTP bool + Banner string +} + +// IsSMTP checks if a host is running a SMTP server. +func (c *SMTPClient) IsSMTP(host string, port int) (IsSMTPResponse, error) { + resp := IsSMTPResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + smtpPlugin := smtp.SMTPPlugin{} + service, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Version + resp.IsSMTP = true + return resp, nil +} diff --git a/v2/pkg/js/libs/ssh/ssh.go b/v2/pkg/js/libs/ssh/ssh.go new file mode 100644 index 00000000..8bc5d124 --- /dev/null +++ b/v2/pkg/js/libs/ssh/ssh.go @@ -0,0 +1,114 @@ +package ssh + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/zmap/zgrab2/lib/ssh" +) + +// SSHClient is a client for SSH servers. +// +// Internally client uses github.com/zmap/zgrab2/lib/ssh driver. +type SSHClient struct{} + +// Connect tries to connect to provided host and port +// with provided username and password with ssh. +// +// Returns state of connection and error. If error is not nil, +// state will be false +func (c *SSHClient) Connect(host string, port int, username, password string) (bool, error) { + conn, err := connect(host, port, username, password, "") + if err != nil { + return false, err + } + defer conn.Close() + + return true, nil +} + +// ConnectWithKey tries to connect to provided host and port +// with provided username and private_key. +// +// Returns state of connection and error. If error is not nil, +// state will be false +func (c *SSHClient) ConnectWithKey(host string, port int, username, key string) (bool, error) { + conn, err := connect(host, port, username, "", key) + if err != nil { + return false, err + } + defer conn.Close() + + return true, nil +} + +// ConnectSSHInfoMode tries to connect to provided host and port +// with provided host and port +// +// Returns HandshakeLog and error. If error is not nil, +// state will be false +// +// HandshakeLog is a struct that contains information about the +// ssh connection +func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog, error) { + return connectSSHInfoMode(host, port) +} + +func connectSSHInfoMode(host string, port int) (*ssh.HandshakeLog, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return nil, protocolstate.ErrHostDenied.Msgf(host) + } + data := new(ssh.HandshakeLog) + + sshConfig := ssh.MakeSSHConfig() + sshConfig.Timeout = 10 * time.Second + sshConfig.ConnLog = data + sshConfig.DontAuthenticate = true + sshConfig.BannerCallback = func(banner string) error { + data.Banner = strings.TrimSpace(banner) + return nil + } + rhost := fmt.Sprintf("%s:%d", host, port) + client, err := ssh.Dial("tcp", rhost, sshConfig) + if err != nil { + return nil, err + } + defer client.Close() + + return data, nil +} + +func connect(host string, port int, user, password, privateKey string) (*ssh.Client, error) { + if !protocolstate.IsHostAllowed(host) { + // host is not valid according to network policy + return nil, protocolstate.ErrHostDenied.Msgf(host) + } + if host == "" || port <= 0 { + return nil, errors.New("invalid host or port") + } + + conf := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{}, + } + if len(password) > 0 { + conf.Auth = append(conf.Auth, ssh.Password(password)) + } + if len(privateKey) > 0 { + signer, err := ssh.ParsePrivateKey([]byte(privateKey)) + if err != nil { + return nil, err + } + conf.Auth = append(conf.Auth, ssh.PublicKeys(signer)) + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), conf) + if err != nil { + return nil, err + } + return client, nil +} diff --git a/v2/pkg/js/libs/structs/smbexploit.js b/v2/pkg/js/libs/structs/smbexploit.js new file mode 100644 index 00000000..80f3b13c --- /dev/null +++ b/v2/pkg/js/libs/structs/smbexploit.js @@ -0,0 +1,97 @@ +const header = bytes.Buffer(); + +// Create the SMB header first +header.append(structs.pack("B", 254)); // magic +header.append("SMB"); +header.append(structs.pack("H", 64)); // header size +header.append(structs.pack("H", 0)); // credit charge +header.append(structs.pack("H", 0)); // channel sequence +header.append(structs.pack("H", 0)); // reserved +header.append(structs.pack("H", 0)); // negotiate protocol command +header.append(structs.pack("H", 31)); // credits requested +header.append(structs.pack("I", 0)); // flags +header.append(structs.pack("I", 0)); // chain offset +header.append(structs.pack("Q", 0)); // message id +header.append(structs.pack("I", 0)); // process id +header.append(structs.pack("I", 0)); // tree id +header.append(structs.pack("Q", 0)); // session id +header.append(structs.pack("QQ", [0, 0])); // signature + +// Create negotiation packet +const negotiation = bytes.Buffer(); +negotiation.append(structs.pack("H", 0x24)); // struct size +negotiation.append(structs.pack("H", 8)); // amount of dialects +negotiation.append(structs.pack("H", 1)); // enable signing +negotiation.append(structs.pack("H", 0)); // reserved +negotiation.append(structs.pack("I", 0x7f)); // capabilities +negotiation.append(structs.pack("QQ", [0, 0])); // client guid +negotiation.append(structs.pack("I", 0x78)); // negotiation offset +negotiation.append(structs.pack("H", 2)); // negotiation context count +negotiation.append(structs.pack("H", 0)); // reserved +negotiation.append(structs.pack("H", 0x0202)); // smb 2.0.2 dialect +negotiation.append(structs.pack("H", 0x0210)); // smb 2.1.0 dialect +negotiation.append(structs.pack("H", 0x0222)); // smb 2.2.2 dialect +negotiation.append(structs.pack("H", 0x0224)); // smb 2.2.4 dialect +negotiation.append(structs.pack("H", 0x0300)); // smb 3.0.0 dialect +negotiation.append(structs.pack("H", 0x0302)); // smb 3.0.2 dialect +negotiation.append(structs.pack("H", 0x0310)); // smb 3.1.0 dialect +negotiation.append(structs.pack("H", 0x0311)); // smb 3.1.1 dialect +negotiation.append(structs.pack("I", 0)); // padding +negotiation.append(structs.pack("H", 1)); // negotiation context type +negotiation.append(structs.pack("H", 38)); // negotiation data length +negotiation.append(structs.pack("I", 0)); // reserved +negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm count +negotiation.append(structs.pack("H", 32)); // negotiation salt length +negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm SHA512 +negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm SHA512 +negotiation.append(structs.pack("QQ", [0, 0])); // salt part 1 +negotiation.append(structs.pack("QQ", [0, 0])); // salt part 2 +negotiation.append(structs.pack("H", 3)); // unknown?? +negotiation.append(structs.pack("H", 10)); // data length unknown?? +negotiation.append(structs.pack("I", 0)); // reserved unknown?? +negotiation.append("\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"); // unknown + +const packet = bytes.Buffer(); +packet.append(header.bytes()); +packet.append(negotiation.bytes()); + +const netbios = bytes.Buffer(); +netbios.append(structs.pack("H", 0)); // NetBIOS sessions message (should be 1 byte but whatever) +netbios.append(structs.pack("B", 0)); // just a pad to make it 3 bytes +netbios.append(structs.pack("B", packet.len())); // NetBIOS length (should be 3 bytes but whatever, as long as the packet isn't 0xff+ bytes) + +const final = bytes.Buffer(); +final.append(netbios.bytes()); +final.append(packet.bytes()); + +console.log("Netbios", netbios.hex(), netbios.len()); +console.log("Header", header.hex(), header.len()); +console.log("Negotation", negotiation.hex(), negotiation.len()); +console.log("Packet", final.hex(), final.len()); + +const c = require("nuclei/libnet"); +let conn = c.Open("tcp", "118.68.186.114:445"); +conn.Send(final.bytes(), 0); +let bytesRecv = conn.Recv(0, 4); +console.log("recv Bytes", bytesRecv); +let size = structs.unpack("I", bytesRecv)[0]; +console.log("Size", size); +let data = conn.Recv(0, size); +console.log("Data", data); + +// TODO: Add hexdump helpers + +version = structs.unpack("H", data.slice(68,70))[0] +context = structs.unpack("H", data.slice(70,72))[0] + +console.log("Version", version); +console.log("Context", context); + +if (version != 0x0311){ + console.log("SMB version ", version, "was found which is not vulnerable!"); +} else if (context != 2) { + console.log("Server answered with context", context, " which indicates that the target may not have SMB compression enabled and is therefore not vulnerable!"); +} else { + console.log("SMB version ", version, " with context ", context, " was found which indicates SMBv3.1.1 is being used and SMB compression is enabled, therefore being vulnerable to CVE-2020-0796!"); +} +conn.Close(); \ No newline at end of file diff --git a/v2/pkg/js/libs/structs/structs.go b/v2/pkg/js/libs/structs/structs.go new file mode 100644 index 00000000..eadc5763 --- /dev/null +++ b/v2/pkg/js/libs/structs/structs.go @@ -0,0 +1,71 @@ +package structs + +import ( + _ "embed" + + "github.com/projectdiscovery/gostruct" +) + +// StructsUnpack the byte slice (presumably packed by Pack(format, msg)) according to the given format. +// The result is a []interface{} slice even if it contains exactly one item. +// The byte slice must contain not less the amount of data required by the format +// (len(msg) must more or equal CalcSize(format)). +// Ex: structs.Unpack(">I", buff[:nb]) +func Unpack(format string, msg []byte) ([]interface{}, error) { + return gostruct.UnPack(buildFormatSliceFromStringFormat(format), msg) +} + +// StructsPack returns a byte slice containing the values of msg slice packed according to the given format. +// The items of msg slice must match the values required by the format exactly. +// Ex: structs.pack("H", 0) +func Pack(formatStr string, msg interface{}) ([]byte, error) { + var args []interface{} + switch v := msg.(type) { + case []interface{}: + args = v + default: + args = []interface{}{v} + } + format := buildFormatSliceFromStringFormat(formatStr) + + for i, f := range format { + if i >= len(args) { + break + } + switch f { + case "h", "H", "i", "I", "l", "L", "q", "Q", "b", "B": + switch v := args[i].(type) { + case int64: + args[i] = int(v) + } + } + } + return gostruct.Pack(format, args) +} + +// StructsCalcSize returns the number of bytes needed to pack the values according to the given format. +func StructsCalcSize(format string) (int, error) { + return gostruct.CalcSize(buildFormatSliceFromStringFormat(format)) +} + +func buildFormatSliceFromStringFormat(format string) []string { + var formats []string + temp := "" + + for _, c := range format { + if c >= '0' && c <= '9' { + temp += string(c) + } else { + if temp != "" { + formats = append(formats, temp+string(c)) + temp = "" + } else { + formats = append(formats, string(c)) + } + } + } + if temp != "" { + formats = append(formats, temp) + } + return formats +} diff --git a/v2/pkg/js/libs/telnet/telnet.go b/v2/pkg/js/libs/telnet/telnet.go new file mode 100644 index 00000000..611b1170 --- /dev/null +++ b/v2/pkg/js/libs/telnet/telnet.go @@ -0,0 +1,45 @@ +package telnet + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/telnet" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// TelnetClient is a minimal Telnet client for nuclei scripts. +type TelnetClient struct{} + +// IsTelnetResponse is the response from the IsTelnet function. +type IsTelnetResponse struct { + IsTelnet bool + Banner string +} + +// IsTelnet checks if a host is running a Telnet server. +func (c *TelnetClient) IsTelnet(host string, port int) (IsTelnetResponse, error) { + resp := IsTelnetResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + telnetPlugin := telnet.TELNETPlugin{} + service, err := telnetPlugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Metadata().(plugins.ServiceTelnet).ServerData + resp.IsTelnet = true + return resp, nil +} diff --git a/v2/pkg/js/libs/vnc/vnc.go b/v2/pkg/js/libs/vnc/vnc.go new file mode 100644 index 00000000..ee0c4cba --- /dev/null +++ b/v2/pkg/js/libs/vnc/vnc.go @@ -0,0 +1,47 @@ +package vnc + +import ( + "context" + "net" + "strconv" + "time" + + "github.com/praetorian-inc/fingerprintx/pkg/plugins" + "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" +) + +// VNCClient is a minimal VNC client for nuclei scripts. +type VNCClient struct{} + +// IsVNCResponse is the response from the IsVNC function. +type IsVNCResponse struct { + IsVNC bool + Banner string +} + +// IsVNC checks if a host is running a VNC server. +// It returns a boolean indicating if the host is running a VNC server +// and the banner of the VNC server. +func (c *VNCClient) IsVNC(host string, port int) (IsVNCResponse, error) { + resp := IsVNCResponse{} + + timeout := 5 * time.Second + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return resp, err + } + defer conn.Close() + + vncPlugin := vnc.VNCPlugin{} + service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host}) + if err != nil { + return resp, err + } + if service == nil { + return resp, nil + } + resp.Banner = service.Version + resp.IsVNC = true + return resp, nil +} diff --git a/v2/pkg/js/utils/util.go b/v2/pkg/js/utils/util.go new file mode 100644 index 00000000..d801a517 --- /dev/null +++ b/v2/pkg/js/utils/util.go @@ -0,0 +1,81 @@ +package utils + +import ( + "database/sql" + + jsoniter "github.com/json-iterator/go" +) + +// UnmarshalSQLRows unmarshals sql rows to json +// +// This function provides a way to unmarshal arbitrary sql rows +// to json. +func UnmarshalSQLRows(rows *sql.Rows) ([]byte, error) { + columnTypes, err := rows.ColumnTypes() + if err != nil { + return nil, err + } + + count := len(columnTypes) + finalRows := []interface{}{} + + for rows.Next() { + + scanArgs := make([]interface{}, count) + + for i, v := range columnTypes { + + switch v.DatabaseTypeName() { + case "VARCHAR", "TEXT", "UUID", "TIMESTAMP": + scanArgs[i] = new(sql.NullString) + case "BOOL": + scanArgs[i] = new(sql.NullBool) + case "INT4": + scanArgs[i] = new(sql.NullInt64) + default: + scanArgs[i] = new(sql.NullString) + } + } + + err := rows.Scan(scanArgs...) + + if err != nil { + return nil, err + } + + masterData := map[string]interface{}{} + + for i, v := range columnTypes { + + if z, ok := (scanArgs[i]).(*sql.NullBool); ok { + masterData[v.Name()] = z.Bool + continue + } + + if z, ok := (scanArgs[i]).(*sql.NullString); ok { + masterData[v.Name()] = z.String + continue + } + + if z, ok := (scanArgs[i]).(*sql.NullInt64); ok { + masterData[v.Name()] = z.Int64 + continue + } + + if z, ok := (scanArgs[i]).(*sql.NullFloat64); ok { + masterData[v.Name()] = z.Float64 + continue + } + + if z, ok := (scanArgs[i]).(*sql.NullInt32); ok { + masterData[v.Name()] = z.Int32 + continue + } + + masterData[v.Name()] = scanArgs[i] + } + + finalRows = append(finalRows, masterData) + } + return jsoniter.Marshal(finalRows) +} diff --git a/v2/pkg/operators/common/dsl/dsl.go b/v2/pkg/operators/common/dsl/dsl.go index 4047330f..eb9e277d 100644 --- a/v2/pkg/operators/common/dsl/dsl.go +++ b/v2/pkg/operators/common/dsl/dsl.go @@ -11,11 +11,14 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns/dnsclientpool" "github.com/projectdiscovery/nuclei/v2/pkg/types" sliceutil "github.com/projectdiscovery/utils/slice" + stringsutil "github.com/projectdiscovery/utils/strings" ) var ( HelperFunctions map[string]govaluate.ExpressionFunction FunctionNames []string + // knownPorts is a list of known ports for protocols implemented in nuclei + knowPorts = []string{"80", "443", "8080", "8081", "8443", "53"} ) func init() { @@ -95,6 +98,20 @@ func init() { return "", fmt.Errorf("no records found") })) + _ = dsl.AddFunction(dsl.NewWithMultipleSignatures("getNetworkPort", []string{ + "(Port string,defaultPort string) string)", + "(Port int,defaultPort int) int", + }, false, func(args ...interface{}) (interface{}, error) { + if len(args) != 2 { + return nil, dsl.ErrInvalidDslFunction + } + port := types.ToString(args[0]) + defaultPort := types.ToString(args[1]) + if port == "" || stringsutil.EqualFoldAny(port, knowPorts...) { + return defaultPort, nil + } + return port, nil + })) dsl.PrintDebugCallback = func(args ...interface{}) error { gologger.Info().Msgf("print_debug value: %s", fmt.Sprint(args)) diff --git a/v2/pkg/operators/matchers/compile.go b/v2/pkg/operators/matchers/compile.go index 5dd3586f..f125c59e 100644 --- a/v2/pkg/operators/matchers/compile.go +++ b/v2/pkg/operators/matchers/compile.go @@ -38,7 +38,7 @@ func (matcher *Matcher) CompileMatchers() error { } // By default, match on body if user hasn't provided any specific items - if matcher.Part == "" { + if matcher.Part == "" && matcher.GetType() != DSLMatcher { matcher.Part = "body" } diff --git a/v2/pkg/output/format_screen.go b/v2/pkg/output/format_screen.go index ab5220f8..6f727c34 100644 --- a/v2/pkg/output/format_screen.go +++ b/v2/pkg/output/format_screen.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/projectdiscovery/nuclei/v2/pkg/types" + mapsutil "github.com/projectdiscovery/utils/maps" ) // formatScreen formats the output for showing on screen. @@ -83,7 +84,9 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) []byte { builder.WriteString(" [") first := true - for name, value := range output.Metadata { + // sort to get predictable output + for _, name := range mapsutil.GetSortedKeys(output.Metadata) { + value := output.Metadata[name] if !first { builder.WriteRune(',') } diff --git a/v2/pkg/parsers/parser.go b/v2/pkg/parsers/parser.go index 2038395c..0056bde5 100644 --- a/v2/pkg/parsers/parser.go +++ b/v2/pkg/parsers/parser.go @@ -11,7 +11,6 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter" "github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates/cache" - "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/utils" "github.com/projectdiscovery/nuclei/v2/pkg/utils/stats" @@ -140,9 +139,12 @@ var ( ) const ( - SyntaxWarningStats = "syntax-warnings" - SyntaxErrorStats = "syntax-errors" - RuntimeWarningsStats = "runtime-warnings" + SyntaxWarningStats = "syntax-warnings" + SyntaxErrorStats = "syntax-errors" + RuntimeWarningsStats = "runtime-warnings" + UnsignedWarning = "unsigned-warnings" + HeadlessFlagWarningStats = "headless-flag-missing-warnings" + TemplatesExecutedStats = "templates-executed" ) func init() { @@ -151,6 +153,9 @@ func init() { stats.NewEntry(SyntaxWarningStats, "Found %d templates with syntax warning (use -validate flag for further examination)") stats.NewEntry(SyntaxErrorStats, "Found %d templates with syntax error (use -validate flag for further examination)") stats.NewEntry(RuntimeWarningsStats, "Found %d templates with runtime error (use -validate flag for further examination)") + stats.NewEntry(UnsignedWarning, "Found %d unsigned or tampered code template (carefully examine before using it & use -sign flag to sign them)") + stats.NewEntry(HeadlessFlagWarningStats, "Excluded %d headless templates (disabled as default), use -headless option to run headless templates.") + stats.NewEntry(TemplatesExecutedStats, "Excluded %d templates with known weak matchers / tags excluded from default run using .nuclei-ignore") } // ParseTemplate parses a template and returns a *templates.Template structure @@ -165,11 +170,6 @@ func ParseTemplate(templatePath string, catalog catalog.Catalog) (*templates.Tem template := &templates.Template{} - // check if the template is verified - if signer.DefaultVerifier != nil { - template.Verified, _ = signer.Verify(signer.DefaultVerifier, data) - } - switch config.GetTemplateFormatFromExt(templatePath) { case config.JSON: err = json.Unmarshal(data, template) @@ -183,7 +183,6 @@ func ParseTemplate(templatePath string, catalog catalog.Catalog) (*templates.Tem err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath) } if err != nil { - stats.Increment(SyntaxErrorStats) return nil, err } diff --git a/v2/pkg/progress/progress.go b/v2/pkg/progress/progress.go index 014be676..d4c9e3c5 100644 --- a/v2/pkg/progress/progress.go +++ b/v2/pkg/progress/progress.go @@ -4,10 +4,7 @@ import ( "context" "encoding/json" "fmt" - "net" - "net/http" "os" - "strconv" "strings" "time" @@ -44,13 +41,12 @@ type StatsTicker struct { cloud bool active bool outputJSON bool - server *http.Server stats clistats.StatisticsClient tickDuration time.Duration } // NewStatsTicker creates and returns a new progress tracking object. -func NewStatsTicker(duration int, active, outputJSON, metrics, cloud bool, port int) (Progress, error) { +func NewStatsTicker(duration int, active, outputJSON, cloud bool, port int) (Progress, error) { var tickDuration time.Duration if active && duration != -1 { tickDuration = time.Duration(duration) * time.Second @@ -60,7 +56,12 @@ func NewStatsTicker(duration int, active, outputJSON, metrics, cloud bool, port progress := &StatsTicker{} - stats, err := clistats.New() + statsOpts := &clistats.DefaultOptions + statsOpts.ListenPort = port + // metrics port is enabled by default and is not configurable with new version of clistats + // by default 63636 is used and than can be modified with -mp flag + + stats, err := clistats.NewWithOptions(context.TODO(), statsOpts) if err != nil { return nil, err } @@ -70,21 +71,6 @@ func NewStatsTicker(duration int, active, outputJSON, metrics, cloud bool, port progress.tickDuration = tickDuration progress.outputJSON = outputJSON - if metrics { - http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { - metrics := progress.getMetrics() - _ = json.NewEncoder(w).Encode(metrics) - }) - progress.server = &http.Server{ - Addr: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), - Handler: http.DefaultServeMux, - } - go func() { - if err := progress.server.ListenAndServe(); err != nil { - gologger.Warning().Msgf("Could not serve metrics: %s", err) - } - }() - } return progress, nil } @@ -110,6 +96,7 @@ func (p *StatsTicker) Init(hostCount int64, rulesCount int, requestCount int64) gologger.Warning().Msgf("Couldn't start statistics: %s", err) } + // Note: this is needed and is responsible for the tick event p.stats.GetStatResponse(p.tickDuration, func(s string, err error) error { if err != nil { gologger.Warning().Msgf("Could not read statistics: %s\n", err) @@ -265,11 +252,6 @@ func metricsMap(stats clistats.StatisticsClient) map[string]interface{} { return results } -// getMetrics returns a map of important metrics for client -func (p *StatsTicker) getMetrics() map[string]interface{} { - return metricsMap(p.stats) -} - // fmtDuration formats the duration for the time elapsed func fmtDuration(d time.Duration) string { d = d.Round(time.Second) @@ -294,7 +276,4 @@ func (p *StatsTicker) Stop() { gologger.Warning().Msgf("Couldn't stop statistics: %s", err) } } - if p.server != nil { - _ = p.server.Shutdown(context.Background()) - } } diff --git a/v2/pkg/protocols/code/code.go b/v2/pkg/protocols/code/code.go new file mode 100644 index 00000000..c048b2da --- /dev/null +++ b/v2/pkg/protocols/code/code.go @@ -0,0 +1,263 @@ +package code + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/pkg/errors" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gozero" + gozerotypes "github.com/projectdiscovery/gozero/types" + "github.com/projectdiscovery/nuclei/v2/pkg/operators" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" + templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + errorutil "github.com/projectdiscovery/utils/errors" +) + +// Request is a request for the SSL protocol +type Request struct { + // Operators for the current request go here. + operators.Operators `yaml:",inline,omitempty"` + CompiledOperators *operators.Operators `yaml:"-"` + + // ID is the optional id of the request + ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID is the optional ID of the Request"` + // description: | + // Engine type + Engine []string `yaml:"engine,omitempty" jsonschema:"title=engine,description=Engine,enum=python,enum=powershell,enum=command"` + // description: | + // Engine Arguments + Args []string `yaml:"args,omitempty" jsonschema:"title=args,description=Args"` + // description: | + // Pattern preferred for file name + Pattern string `yaml:"pattern,omitempty" jsonschema:"title=pattern,description=Pattern"` + // description: | + // Source File/Snippet + Source string `yaml:"source,omitempty" jsonschema:"title=source file/snippet,description=Source snippet"` + + options *protocols.ExecutorOptions + gozero *gozero.Gozero + src *gozero.Source +} + +// Compile compiles the request generators preparing any requests possible. +func (request *Request) Compile(options *protocols.ExecutorOptions) error { + request.options = options + + gozeroOptions := &gozero.Options{ + Engines: request.Engine, + Args: request.Args, + EarlyCloseFileDescriptor: true, + } + engine, err := gozero.New(gozeroOptions) + if err != nil { + return errorutil.NewWithErr(err).Msgf("[%s] engines '%s' not available on host", options.TemplateID, strings.Join(request.Engine, ",")) + } + request.gozero = engine + + var src *gozero.Source + + src, err = gozero.NewSourceWithString(request.Source, request.Pattern) + if err != nil { + return err + } + request.src = src + + if len(request.Matchers) > 0 || len(request.Extractors) > 0 { + compiled := &request.Operators + compiled.ExcludeMatchers = options.ExcludeMatchers + compiled.TemplateID = options.TemplateID + if err := compiled.Compile(); err != nil { + return errors.Wrap(err, "could not compile operators") + } + for _, matcher := range compiled.Matchers { + // default matcher part for code protocol is response + if matcher.Part == "" || matcher.Part == "body" { + matcher.Part = "response" + } + } + for _, extractor := range compiled.Extractors { + // default extractor part for code protocol is response + if extractor.Part == "" || extractor.Part == "body" { + extractor.Part = "response" + } + } + request.CompiledOperators = compiled + } + return nil +} + +// Requests returns the total number of requests the rule will perform +func (request *Request) Requests() int { + return 1 +} + +// GetID returns the ID for the request if any. +func (request *Request) GetID() string { + return request.ID +} + +// ExecuteWithResults executes the protocol requests and returns results instead of writing them. +func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { + metaSrc, err := gozero.NewSourceWithString(input.MetaInput.Input, "") + if err != nil { + return err + } + defer func() { + if err := metaSrc.Cleanup(); err != nil { + gologger.Warning().Msgf("%s\n", err) + } + }() + + var interactshURLs []string + + // inject all template context values as gozero env variables + variables := protocolutils.GenerateVariables(input.MetaInput.Input, false, nil) + // add template context values + variables = generators.MergeMaps(variables, request.options.GetTemplateCtx(input.MetaInput).GetAll()) + // optionvars are vars passed from CLI or env variables + optionVars := generators.BuildPayloadFromOptions(request.options.Options) + variablesMap := request.options.Variables.Evaluate(variables) + variables = generators.MergeMaps(variablesMap, variables, optionVars, request.options.Constants) + for name, value := range variables { + v := fmt.Sprint(value) + v, interactshURLs = request.options.Interactsh.Replace(v, interactshURLs) + metaSrc.AddVariable(gozerotypes.Variable{Name: name, Value: v}) + } + gOutput, err := request.gozero.Eval(context.Background(), request.src, metaSrc) + if err != nil { + return err + } + gologger.Verbose().Msgf("[%s] Executed code on local machine %v", request.options.TemplateID, input.MetaInput.Input) + + if vardump.EnableVarDump { + gologger.Debug().Msgf("Code Protocol request variables: \n%s\n", vardump.DumpVariables(variables)) + } + + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("[%s] Dumped Executed Source Code for %v\n\n%v\n", request.options.TemplateID, input.MetaInput.Input, request.Source) + } + + dataOutputString := fmtStdout(gOutput.Stdout.String()) + + data := make(output.InternalEvent) + + data["type"] = request.Type().String() + data["response"] = dataOutputString // response contains filtered output (eg without trailing \n) + data["input"] = input.MetaInput.Input + data["template-path"] = request.options.TemplatePath + data["template-id"] = request.options.TemplateID + data["template-info"] = request.options.TemplateInfo + if gOutput.Stderr.Len() > 0 { + data["stderr"] = fmtStdout(gOutput.Stderr.String()) + } + + // expose response variables in proto_var format + // this is no-op if the template is not a multi protocol template + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, data) + + // add variables from template context before matching/extraction + data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll()) + + if request.options.Interactsh != nil { + request.options.Interactsh.MakePlaceholders(interactshURLs, data) + } + + // todo #1: interactsh async callback should be eliminated as it lead to ton of code duplication + // todo #2: various structs InternalWrappedEvent, InternalEvent should be unwrapped and merged into minimal callbacks and a unique struct (eg. event?) + event := eventcreator.CreateEvent(request, data, request.options.Options.Debug || request.options.Options.DebugResponse) + if request.options.Interactsh != nil { + event.UsesInteractsh = true + request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{ + MakeResultFunc: request.MakeResultEvent, + Event: event, + Operators: request.CompiledOperators, + MatchFunc: request.Match, + ExtractFunc: request.Extract, + }) + } + + if request.options.Options.Debug || request.options.Options.DebugResponse || request.options.Options.StoreResponse { + msg := fmt.Sprintf("[%s] Dumped Code Execution for %s\n\n", request.options.TemplateID, input.MetaInput.Input) + if request.options.Options.Debug || request.options.Options.DebugResponse { + gologger.Debug().Msg(msg) + gologger.Print().Msgf("%s\n\n", responsehighlighter.Highlight(event.OperatorsResult, dataOutputString, request.options.Options.NoColor, false)) + } + if request.options.Options.StoreResponse { + request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dataOutputString)) + } + } + + callback(event) + + return nil +} + +// RequestPartDefinitions contains a mapping of request part definitions and their +// description. Multiple definitions are separated by commas. +// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>. +var RequestPartDefinitions = map[string]string{ + "type": "Type is the type of request made", + "host": "Host is the input to the template", + "matched": "Matched is the input which was matched upon", +} + +// Match performs matching operation for a matcher on model and returns: +// true and a list of matched snippets if the matcher type is supports it +// otherwise false and an empty string slice +func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) { + return protocols.MakeDefaultMatchFunc(data, matcher) +} + +// Extract performs extracting operation for an extractor on model and returns true or false. +func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} { + return protocols.MakeDefaultExtractFunc(data, matcher) +} + +// MakeResultEvent creates a result event from internal wrapped event +func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent { + return protocols.MakeDefaultResultEvent(request, wrapped) +} + +// GetCompiledOperators returns a list of the compiled operators +func (request *Request) GetCompiledOperators() []*operators.Operators { + return []*operators.Operators{request.CompiledOperators} +} + +// Type returns the type of the protocol request +func (request *Request) Type() templateTypes.ProtocolType { + return templateTypes.CodeProtocol +} + +func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { + data := &output.ResultEvent{ + TemplateID: types.ToString(request.options.TemplateID), + TemplatePath: types.ToString(request.options.TemplatePath), + Info: request.options.TemplateInfo, + Type: types.ToString(wrapped.InternalEvent["type"]), + Matched: types.ToString(wrapped.InternalEvent["input"]), + Metadata: wrapped.OperatorsResult.PayloadValues, + ExtractedResults: wrapped.OperatorsResult.OutputExtracts, + Timestamp: time.Now(), + MatcherStatus: true, + } + return data +} + +func fmtStdout(data string) string { + return strings.Trim(data, " \n\r\t") +} diff --git a/v2/pkg/protocols/code/code_test.go b/v2/pkg/protocols/code/code_test.go new file mode 100644 index 00000000..001c2430 --- /dev/null +++ b/v2/pkg/protocols/code/code_test.go @@ -0,0 +1,40 @@ +//go:build linux || darwin + +package code + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/projectdiscovery/nuclei/v2/pkg/model" + "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" +) + +func TestCodeProtocol(t *testing.T) { + options := testutils.DefaultOptions + + testutils.Init(options) + templateID := "testing-code" + request := &Request{ + Engine: []string{"sh"}, + Source: "echo test", + } + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: templateID, + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + err := request.Compile(executerOpts) + require.Nil(t, err, "could not compile code request") + + var gotEvent output.InternalEvent + ctxArgs := contextargs.NewWithInput("") + err = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) { + gotEvent = event.InternalEvent + }) + require.Nil(t, err, "could not run code request") + require.NotEmpty(t, gotEvent, "could not get event items") +} diff --git a/v2/pkg/protocols/common/contextargs/contextargs.go b/v2/pkg/protocols/common/contextargs/contextargs.go index 72b21ea4..4ebaa156 100644 --- a/v2/pkg/protocols/common/contextargs/contextargs.go +++ b/v2/pkg/protocols/common/contextargs/contextargs.go @@ -3,7 +3,9 @@ package contextargs import ( "net/http/cookiejar" "strings" + "sync/atomic" + "github.com/projectdiscovery/gologger" mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" @@ -29,33 +31,62 @@ type Context struct { // Create a new contextargs instance func New() *Context { - return &Context{MetaInput: &MetaInput{}} + return NewWithInput("") } // Create a new contextargs instance with input string func NewWithInput(input string) *Context { - return &Context{MetaInput: &MetaInput{Input: input}} -} - -func (ctx *Context) initialize() { - ctx.args = &mapsutil.SyncLockMap[string, interface{}]{Map: mapsutil.Map[string, interface{}]{}} + jar, err := cookiejar.New(nil) + if err != nil { + gologger.Error().Msgf("contextargs: could not create cookie jar: %s\n", err) + } + return &Context{ + MetaInput: &MetaInput{Input: input}, + CookieJar: jar, + args: &mapsutil.SyncLockMap[string, interface{}]{ + Map: make(map[string]interface{}), + ReadOnly: atomic.Bool{}, + }, + } } // Set the specific key-value pair func (ctx *Context) Set(key string, value interface{}) { - if !ctx.isInitialized() { - ctx.initialize() - } - _ = ctx.args.Set(key, value) } -func (ctx *Context) isInitialized() bool { - return ctx.args != nil +func (ctx *Context) hasArgs() bool { + return !ctx.args.IsEmpty() } -func (ctx *Context) hasArgs() bool { - return ctx.isInitialized() && !ctx.args.IsEmpty() +// Merge the key-value pairs +func (ctx *Context) Merge(args map[string]interface{}) { + _ = ctx.args.Merge(args) +} + +// Add the specific key-value pair +func (ctx *Context) Add(key string, v interface{}) { + values, ok := ctx.args.Get(key) + if !ok { + ctx.Set(key, v) + } + + // If the key exists, append the value to the existing value + switch v := v.(type) { + case []string: + if values, ok := values.([]string); ok { + values = append(values, v...) + ctx.Set(key, values) + } + case string: + if values, ok := values.(string); ok { + tmp := []string{values, v} + ctx.Set(key, tmp) + } + default: + values, _ := ctx.Get(key) + ctx.Set(key, []interface{}{values, v}) + } } // UseNetworkPort updates input with required/default network port for that template @@ -83,6 +114,15 @@ func (ctx *Context) UseNetworkPort(port string, excludePorts string) error { return nil } +// Port returns the port of the target +func (ctx *Context) Port() string { + target, err := urlutil.Parse(ctx.MetaInput.Input) + if err != nil { + return "" + } + return target.Port() +} + // Get the value with specific key if exists func (ctx *Context) Get(key string) (interface{}, bool) { if !ctx.hasArgs() { @@ -92,12 +132,12 @@ func (ctx *Context) Get(key string) (interface{}, bool) { return ctx.args.Get(key) } -func (ctx *Context) GetAll() *mapsutil.SyncLockMap[string, interface{}] { +func (ctx *Context) GetAll() map[string]interface{} { if !ctx.hasArgs() { return nil } - return ctx.args.Clone() + return ctx.args.Clone().Map } func (ctx *Context) ForEach(f func(string, interface{})) { @@ -113,13 +153,13 @@ func (ctx *Context) Has(key string) bool { } func (ctx *Context) HasArgs() bool { - return ctx.hasArgs() + return !ctx.args.IsEmpty() } func (ctx *Context) Clone() *Context { newCtx := &Context{ MetaInput: ctx.MetaInput.Clone(), - args: ctx.args, + args: ctx.args.Clone(), CookieJar: ctx.CookieJar, } return newCtx diff --git a/v2/pkg/protocols/common/contextargs/metainput.go b/v2/pkg/protocols/common/contextargs/metainput.go index b76603de..4027bd7d 100644 --- a/v2/pkg/protocols/common/contextargs/metainput.go +++ b/v2/pkg/protocols/common/contextargs/metainput.go @@ -2,6 +2,7 @@ package contextargs import ( "bytes" + "crypto/md5" "fmt" "strings" @@ -14,6 +15,8 @@ type MetaInput struct { Input string `json:"input,omitempty"` // CustomIP to use for connection CustomIP string `json:"customIP,omitempty"` + // hash of the input + hash string `json:"-"` } func (metaInput *MetaInput) marshalToBuffer() (bytes.Buffer, error) { @@ -67,3 +70,19 @@ func (metaInput *MetaInput) PrettyPrint() string { } return metaInput.Input } + +// GetScanHash returns a unique hash that represents a scan by hashing (metainput + templateId) +func (metaInput *MetaInput) GetScanHash(templateId string) string { + // there may be some cases where metainput is changed ex: while executing self-contained template etc + // but that totally changes the scanID/hash so to avoid that we compute hash only once + // and reuse it for all subsequent calls + if metaInput.hash == "" { + metaInput.hash = getMd5Hash(templateId + ":" + metaInput.Input + ":" + metaInput.CustomIP) + } + return metaInput.hash +} + +func getMd5Hash(data string) string { + bin := md5.Sum([]byte(data)) + return string(bin[:]) +} diff --git a/v2/pkg/protocols/common/executer/executer.go b/v2/pkg/protocols/common/executer/executer.go deleted file mode 100644 index bfdf2242..00000000 --- a/v2/pkg/protocols/common/executer/executer.go +++ /dev/null @@ -1,195 +0,0 @@ -package executer - -import ( - "fmt" - "strings" - "sync" - "sync/atomic" - - "github.com/pkg/errors" - - "github.com/projectdiscovery/gologger" - "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" - "github.com/projectdiscovery/nuclei/v2/pkg/output" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/writer" -) - -// Executer executes a group of requests for a protocol -type Executer struct { - requests []protocols.Request - options *protocols.ExecutorOptions -} - -var _ protocols.Executer = &Executer{} - -// NewExecuter creates a new request executer for list of requests -func NewExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) *Executer { - return &Executer{requests: requests, options: options} -} - -// Compile compiles the execution generators preparing any requests possible. -func (e *Executer) Compile() error { - cliOptions := e.options.Options - - for _, request := range e.requests { - if err := request.Compile(e.options); err != nil { - var dslCompilationError *dsl.CompilationError - if errors.As(err, &dslCompilationError) { - if cliOptions.Verbose { - rawErrorMessage := dslCompilationError.Error() - formattedErrorMessage := strings.ToUpper(rawErrorMessage[:1]) + rawErrorMessage[1:] + "." - gologger.Warning().Msgf(formattedErrorMessage) - gologger.Info().Msgf("The available custom DSL functions are:") - fmt.Println(dsl.GetPrintableDslFunctionSignatures(cliOptions.NoColor)) - } - } - return err - } - } - return nil -} - -// Requests returns the total number of requests the rule will perform -func (e *Executer) Requests() int { - var count int - for _, request := range e.requests { - count += request.Requests() - } - return count -} - -// Execute executes the protocol group and returns true or false if results were found. -func (e *Executer) Execute(input *contextargs.Context) (bool, error) { - results := &atomic.Bool{} - - dynamicValues := make(map[string]interface{}) - if input.HasArgs() { - input.ForEach(func(key string, value interface{}) { - dynamicValues[key] = value - }) - } - previous := make(map[string]interface{}) - - mtx := &sync.Mutex{} - var lastMatcherEvent *output.InternalWrappedEvent - writeFailureCallback := func(event *output.InternalWrappedEvent, matcherStatus bool) { - if !results.Load() && matcherStatus { - if err := e.options.Output.WriteFailure(event); err != nil { - gologger.Warning().Msgf("Could not write failure event to output: %s\n", err) - } - results.Store(true) - } - } - - for _, req := range e.requests { - inputItem := input.Clone() - if e.options.InputHelper != nil && input.MetaInput.Input != "" { - if inputItem.MetaInput.Input = e.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" { - return false, nil - } - } - - err := req.ExecuteWithResults(inputItem, dynamicValues, previous, func(event *output.InternalWrappedEvent) { - ID := req.GetID() - if ID != "" { - builder := &strings.Builder{} - for k, v := range event.InternalEvent { - builder.WriteString(ID) - builder.WriteString("_") - builder.WriteString(k) - previous[builder.String()] = v - builder.Reset() - } - } - // If no results were found, and also interactsh is not being used - // in that case we can skip it, otherwise we've to show failure in - // case of matcher-status flag. - if !event.HasOperatorResult() && !event.UsesInteractsh { - mtx.Lock() - lastMatcherEvent = event - mtx.Unlock() - } else { - if !(event.UsesInteractsh && event.InteractshMatched.Load()) && writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient) { - if event.UsesInteractsh { - results.Store(true) - } - results.Store(true) - } else { - mtx.Lock() - lastMatcherEvent = event - mtx.Unlock() - } - } - }) - if err != nil { - if e.options.HostErrorsCache != nil { - e.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err) - } - gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", e.options.TemplateID, input.MetaInput.PrettyPrint(), err) - } - // If a match was found and stop at first match is set, break out of the loop and return - if results.Load() && (e.options.StopAtFirstMatch || e.options.Options.StopAtFirstMatch) { - break - } - } - if lastMatcherEvent != nil { - writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus) - } - return results.Load(), nil -} - -// Deprecated: Use Execute instead along with outputWriter.callback https://github.com/projectdiscovery/nuclei/issues/4054 will include -// abstraction for this in future. -func (e *Executer) ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error { - dynamicValues := make(map[string]interface{}) - if input.HasArgs() { - input.ForEach(func(key string, value interface{}) { - dynamicValues[key] = value - }) - } - previous := make(map[string]interface{}) - results := &atomic.Bool{} - - for _, req := range e.requests { - req := req - - inputItem := input.Clone() - if e.options.InputHelper != nil && input.MetaInput.Input != "" { - if inputItem.MetaInput.Input = e.options.InputHelper.Transform(input.MetaInput.Input, req.Type()); input.MetaInput.Input == "" { - return nil - } - } - - err := req.ExecuteWithResults(inputItem, dynamicValues, previous, func(event *output.InternalWrappedEvent) { - ID := req.GetID() - if ID != "" { - builder := &strings.Builder{} - for k, v := range event.InternalEvent { - builder.WriteString(ID) - builder.WriteString("_") - builder.WriteString(k) - previous[builder.String()] = v - builder.Reset() - } - } - if event.OperatorsResult == nil { - return - } - results.Store(true) - callback(event) - }) - if err != nil { - if e.options.HostErrorsCache != nil { - e.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err) - } - gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", e.options.TemplateID, input.MetaInput.PrettyPrint(), err) - } - // If a match was found and stop at first match is set, break out of the loop and return - if results.Load() && (e.options.StopAtFirstMatch || e.options.Options.StopAtFirstMatch) { - break - } - } - return nil -} diff --git a/v2/pkg/protocols/common/generators/generators.go b/v2/pkg/protocols/common/generators/generators.go index 98fc6aa1..c17fef84 100644 --- a/v2/pkg/protocols/common/generators/generators.go +++ b/v2/pkg/protocols/common/generators/generators.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v2/pkg/catalog" - "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/types" ) // PayloadGenerator is the generator struct for generating payloads @@ -14,10 +14,11 @@ type PayloadGenerator struct { Type AttackType catalog catalog.Catalog payloads map[string][]string + options *types.Options } // New creates a new generator structure for payload generation -func New(payloads map[string]interface{}, attackType AttackType, templatePath string, allowLocalFileAccess bool, catalog catalog.Catalog, customAttackType string) (*PayloadGenerator, error) { +func New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog catalog.Catalog, customAttackType string, opts *types.Options) (*PayloadGenerator, error) { if attackType.String() == "" { attackType = BatteringRamAttack } @@ -38,12 +39,12 @@ func New(payloads map[string]interface{}, attackType AttackType, templatePath st } } - generator := &PayloadGenerator{catalog: catalog} + generator := &PayloadGenerator{catalog: catalog, options: opts} if err := generator.validate(payloadsFinal, templatePath); err != nil { return nil, err } - compiled, err := generator.loadPayloads(payloadsFinal, templatePath, config.DefaultConfig.TemplatesDirectory, allowLocalFileAccess) + compiled, err := generator.loadPayloads(payloadsFinal, templatePath) if err != nil { return nil, err } diff --git a/v2/pkg/protocols/common/generators/generators_test.go b/v2/pkg/protocols/common/generators/generators_test.go index 71878089..2226188d 100644 --- a/v2/pkg/protocols/common/generators/generators_test.go +++ b/v2/pkg/protocols/common/generators/generators_test.go @@ -6,13 +6,14 @@ import ( "github.com/stretchr/testify/require" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/types" ) func TestBatteringRamGenerator(t *testing.T) { usernames := []string{"admin", "password"} catalogInstance := disk.NewCatalog("") - generator, err := New(map[string]interface{}{"username": usernames}, BatteringRamAttack, "", false, catalogInstance, "") + generator, err := New(map[string]interface{}{"username": usernames}, BatteringRamAttack, "", catalogInstance, "", getOptions(false)) require.Nil(t, err, "could not create generator") iterator := generator.NewIterator() @@ -32,7 +33,7 @@ func TestPitchforkGenerator(t *testing.T) { passwords := []string{"password1", "password2", "password3"} catalogInstance := disk.NewCatalog("") - generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, PitchForkAttack, "", false, catalogInstance, "") + generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, PitchForkAttack, "", catalogInstance, "", getOptions(false)) require.Nil(t, err, "could not create generator") iterator := generator.NewIterator() @@ -54,7 +55,7 @@ func TestClusterbombGenerator(t *testing.T) { passwords := []string{"admin", "password", "token"} catalogInstance := disk.NewCatalog("") - generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, ClusterBombAttack, "", false, catalogInstance, "") + generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, ClusterBombAttack, "", catalogInstance, "", getOptions(false)) require.Nil(t, err, "could not create generator") iterator := generator.NewIterator() @@ -83,3 +84,9 @@ func TestClusterbombGenerator(t *testing.T) { } require.Equal(t, 3, count, "could not get correct clusterbomb counts") } + +func getOptions(allowLocalFileAccess bool) *types.Options { + opts := types.DefaultOptions() + opts.AllowLocalFileAccess = allowLocalFileAccess + return opts +} diff --git a/v2/pkg/protocols/common/generators/load.go b/v2/pkg/protocols/common/generators/load.go index 92ec9320..390c8141 100644 --- a/v2/pkg/protocols/common/generators/load.go +++ b/v2/pkg/protocols/common/generators/load.go @@ -2,7 +2,7 @@ package generators import ( "bufio" - "path/filepath" + "io" "strings" "github.com/pkg/errors" @@ -11,7 +11,7 @@ import ( ) // loadPayloads loads the input payloads from a map to a data map -func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, templatePath, templateDirectory string, allowLocalFileAccess bool) (map[string][]string, error) { +func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, templatePath string) (map[string][]string, error) { loadedPayloads := make(map[string][]string) for name, payload := range payloads { @@ -22,18 +22,11 @@ func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, if len(elements) >= 2 { loadedPayloads[name] = elements } else { - if !allowLocalFileAccess { - pt = filepath.Clean(pt) - templateAbsPath, err := filepath.Abs(templatePath) - if err != nil { - return nil, errors.Wrap(err, "could not get absolute path") - } - templatePathDir := filepath.Dir(templateAbsPath) - if !(templatePathDir != "/" && strings.HasPrefix(pt, templatePathDir)) && !strings.HasPrefix(pt, templateDirectory) { - return nil, errors.New("denied payload file path specified") - } + file, err := generator.options.LoadHelperFile(pt, templatePath, generator.catalog) + if err != nil { + return nil, errors.Wrap(err, "could not load payload file") } - payloads, err := generator.loadPayloadsFromFile(pt) + payloads, err := generator.loadPayloadsFromFile(file) if err != nil { return nil, errors.Wrap(err, "could not load payloads") } @@ -47,13 +40,8 @@ func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, } // loadPayloadsFromFile loads a file to a string slice -func (generator *PayloadGenerator) loadPayloadsFromFile(filepath string) ([]string, error) { +func (generator *PayloadGenerator) loadPayloadsFromFile(file io.ReadCloser) ([]string, error) { var lines []string - - file, err := generator.catalog.OpenFile(filepath) - if err != nil { - return nil, err - } defer file.Close() scanner := bufio.NewScanner(file) diff --git a/v2/pkg/protocols/common/generators/load_test.go b/v2/pkg/protocols/common/generators/load_test.go index 28803b09..eed974e1 100644 --- a/v2/pkg/protocols/common/generators/load_test.go +++ b/v2/pkg/protocols/common/generators/load_test.go @@ -2,66 +2,119 @@ package generators import ( "os" + "os/exec" "path/filepath" "testing" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" osutils "github.com/projectdiscovery/utils/os" "github.com/stretchr/testify/require" ) func TestLoadPayloads(t *testing.T) { - tempdir, err := os.MkdirTemp("", "templates-*") - require.NoError(t, err, "could not create temp dir") - defer os.RemoveAll(tempdir) + // since we are changing value of global variable i.e templates directory + // run this test as subprocess + if os.Getenv("LOAD_PAYLOAD_NO_ACCESS") != "1" { + cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess") + cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_NO_ACCESS=1") + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + if err != nil { + t.Fatalf("process ran with err %v, want exit status 1", err) + } + } + templateDir := getTemplatesDir(t) + config.DefaultConfig.SetTemplatesDir(templateDir) - generator := &PayloadGenerator{catalog: disk.NewCatalog(tempdir)} - - fullpath := filepath.Join(tempdir, "payloads.txt") - err = os.WriteFile(fullpath, []byte("test\nanother"), 0777) - require.NoError(t, err, "could not write payload") + generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(false)} + fullpath := filepath.Join(templateDir, "payloads.txt") // Test sandbox t.Run("templates-directory", func(t *testing.T) { + // testcase when loading file from template directory and template file is in root + // expected to succeed values, err := generator.loadPayloads(map[string]interface{}{ "new": fullpath, - }, "/test", tempdir, false) + }, "/test") require.NoError(t, err, "could not load payloads") require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values") }) t.Run("templates-path-relative", func(t *testing.T) { + // testcase when loading file from template directory and template file is current working directory + // expected to fail since this is LFI _, err := generator.loadPayloads(map[string]interface{}{ "new": "../../../../../../../../../etc/passwd", - }, ".", tempdir, false) + }, ".") require.Error(t, err, "could load payloads") }) t.Run("template-directory", func(t *testing.T) { + // testcase when loading file from template directory and template file is inside template directory + // expected to succeed values, err := generator.loadPayloads(map[string]interface{}{ "new": fullpath, - }, filepath.Join(tempdir, "test.yaml"), "/test", false) + }, filepath.Join(templateDir, "test.yaml")) require.NoError(t, err, "could not load payloads") require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values") }) + + t.Run("invalid", func(t *testing.T) { + // testcase when loading file from /etc/passwd and template file is at root i.e / + // expected to fail since this is LFI + values, err := generator.loadPayloads(map[string]interface{}{ + "new": "/etc/passwd", + }, "/random") + require.Error(t, err, "could load payloads got %v", values) + require.Equal(t, 0, len(values), "could get values") + + // testcase when loading file from template directory and template file is at root i.e / + // expected to succeed + values, err = generator.loadPayloads(map[string]interface{}{ + "new": fullpath, + }, "/random") + require.NoError(t, err, "could load payloads %v", values) + require.Equal(t, 1, len(values), "could get values") + require.Equal(t, []string{"test", "another"}, values["new"], "could get values") + }) +} + +func TestLoadPayloadsWithAccess(t *testing.T) { + // since we are changing value of global variable i.e templates directory + // run this test as subprocess + if os.Getenv("LOAD_PAYLOAD_WITH_ACCESS") != "1" { + cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess") + cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_WITH_ACCESS=1") + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + if err != nil { + t.Fatalf("process ran with err %v, want exit status 1", err) + } + } + templateDir := getTemplatesDir(t) + config.DefaultConfig.SetTemplatesDir(templateDir) + + generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(true)} + t.Run("no-sandbox-unix", func(t *testing.T) { if osutils.IsWindows() { return } _, err := generator.loadPayloads(map[string]interface{}{ "new": "/etc/passwd", - }, "/random", "/test", true) + }, "/random") require.NoError(t, err, "could load payloads") }) - t.Run("invalid", func(t *testing.T) { - values, err := generator.loadPayloads(map[string]interface{}{ - "new": "/etc/passwd", - }, "/random", "/test", false) - require.Error(t, err, "could load payloads") - require.Equal(t, 0, len(values), "could get values") - - values, err = generator.loadPayloads(map[string]interface{}{ - "new": fullpath, - }, "/random", "/test", false) - require.Error(t, err, "could load payloads") - require.Equal(t, 0, len(values), "could get values") - }) +} + +func getTemplatesDir(t *testing.T) string { + tempdir, err := os.MkdirTemp("", "templates-*") + require.NoError(t, err, "could not create temp dir") + fullpath := filepath.Join(tempdir, "payloads.txt") + err = os.WriteFile(fullpath, []byte("test\nanother"), 0777) + require.NoError(t, err, "could not write payload") + return tempdir } diff --git a/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go b/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go index 371de992..1480de5c 100644 --- a/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go +++ b/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go @@ -6,6 +6,8 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // CreateEvent wraps the outputEvent with the result of the operators defined on the request @@ -21,7 +23,8 @@ func CreateEventWithAdditionalOptions(request protocols.Request, outputEvent out // Dump response variables if ran in debug mode if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol response variables: \n%s\n", vardump.DumpVariables(outputEvent)) + protoName := cases.Title(language.English).String(request.Type().String()) + gologger.Debug().Msgf("%v Protocol response variables: \n%s\n", protoName, vardump.DumpVariables(outputEvent)) } for _, compiledOperator := range request.GetCompiledOperators() { if compiledOperator != nil { diff --git a/v2/pkg/protocols/common/protocolstate/file.go b/v2/pkg/protocols/common/protocolstate/file.go new file mode 100644 index 00000000..cc837a68 --- /dev/null +++ b/v2/pkg/protocols/common/protocolstate/file.go @@ -0,0 +1,34 @@ +package protocolstate + +import ( + "strings" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + errorutil "github.com/projectdiscovery/utils/errors" + fileutil "github.com/projectdiscovery/utils/file" +) + +var ( + // lfaAllowed means local file access is allowed + lfaAllowed bool +) + +// Normalizepath normalizes path and returns absolute path +// it returns error if path is not allowed +// this respects the sandbox rules and only loads files from +// allowed directories +func NormalizePath(filePath string) (string, error) { + if lfaAllowed { + return filePath, nil + } + cleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir()) + if err != nil { + return "", errorutil.NewWithErr(err).Msgf("could not resolve and clean path %v", filePath) + } + // only allow files inside nuclei-templates directory + // even current working directory is not allowed + if strings.HasPrefix(cleaned, config.DefaultConfig.GetTemplateDir()) { + return cleaned, nil + } + return "", errorutil.New("path %v is outside nuclei-template directory and -lfa is not enabled", filePath) +} diff --git a/v2/pkg/protocols/common/protocolstate/headless.go b/v2/pkg/protocols/common/protocolstate/headless.go index 36689b3b..77e5d52c 100644 --- a/v2/pkg/protocols/common/protocolstate/headless.go +++ b/v2/pkg/protocols/common/protocolstate/headless.go @@ -16,6 +16,7 @@ import ( var ( ErrURLDenied = errorutil.NewWithFmt("headless: url %v dropped by rule: %v") + ErrHostDenied = errorutil.NewWithFmt("host %v dropped by network policy") networkPolicy *networkpolicy.NetworkPolicy allowLocalFileAccess bool ) @@ -77,3 +78,12 @@ func isValidHost(targetUrl string) bool { _, ok := networkPolicy.ValidateHost(targetUrl) return ok } + +// IsHostAllowed checks if the host is allowed by network policy +func IsHostAllowed(targetUrl string) bool { + if networkPolicy == nil { + return true + } + _, ok := networkPolicy.ValidateHost(targetUrl) + return ok +} diff --git a/v2/pkg/protocols/common/protocolstate/js.go b/v2/pkg/protocols/common/protocolstate/js.go new file mode 100644 index 00000000..9e522db4 --- /dev/null +++ b/v2/pkg/protocols/common/protocolstate/js.go @@ -0,0 +1,20 @@ +package protocolstate + +import ( + "github.com/dop251/goja" + "github.com/dop251/goja/parser" + "github.com/projectdiscovery/gologger" +) + +// NewJSRuntime returns a new javascript runtime +// with defaults set +// i.e sourcemap parsing is disabled by default +func NewJSRuntime() *goja.Runtime { + vm := goja.New() + vm.SetParserOptions(parser.WithDisableSourceMaps) + // disable eval by default + if err := vm.Set("eval", "undefined"); err != nil { + gologger.Error().Msgf("could not set eval to undefined: %s", err) + } + return vm +} diff --git a/v2/pkg/protocols/common/protocolstate/state.go b/v2/pkg/protocols/common/protocolstate/state.go index d07d1ba1..4c97414f 100644 --- a/v2/pkg/protocols/common/protocolstate/state.go +++ b/v2/pkg/protocols/common/protocolstate/state.go @@ -21,6 +21,7 @@ func Init(options *types.Options) error { if Dialer != nil { return nil } + lfaAllowed = options.AllowLocalFileAccess opts := fastdialer.DefaultOptions InitHeadless(options.RestrictLocalNetworkAccess, options.AllowLocalFileAccess) diff --git a/v2/pkg/protocols/common/randomip/randomip.go b/v2/pkg/protocols/common/randomip/randomip.go index 69fae2c7..8c932198 100644 --- a/v2/pkg/protocols/common/randomip/randomip.go +++ b/v2/pkg/protocols/common/randomip/randomip.go @@ -63,7 +63,10 @@ func getRandomIP(ipnet *net.IPNet, size int) net.IP { return ip } - _, _ = rand.Read(r) + _, err := rand.Read(r) + if err != nil { + break + } for i := 0; i <= quotient; i++ { if i == quotient { diff --git a/v2/pkg/protocols/common/utils/vardump/dump.go b/v2/pkg/protocols/common/utils/vardump/dump.go index 7070956b..03960aa6 100644 --- a/v2/pkg/protocols/common/utils/vardump/dump.go +++ b/v2/pkg/protocols/common/utils/vardump/dump.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/projectdiscovery/nuclei/v2/pkg/types" + mapsutil "github.com/projectdiscovery/utils/maps" ) // EnableVarDump enables var dump for debugging optionally @@ -21,7 +22,11 @@ func DumpVariables(data map[string]interface{}) string { buffer.Grow(len(data) * 78) // grow buffer to an approximate size builder := &strings.Builder{} - for k, v := range data { + // sort keys for deterministic output + keys := mapsutil.GetSortedKeys(data) + + for _, k := range keys { + v := data[k] valueString := types.ToString(v) counter++ diff --git a/v2/pkg/protocols/common/variables/variables.go b/v2/pkg/protocols/common/variables/variables.go index b806402a..7a7fda1f 100644 --- a/v2/pkg/protocols/common/variables/variables.go +++ b/v2/pkg/protocols/common/variables/variables.go @@ -66,6 +66,11 @@ func (variables *Variable) UnmarshalJSON(data []byte) error { func (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} { result := make(map[string]interface{}, variables.Len()) variables.ForEach(func(key string, value interface{}) { + if sliceValue, ok := value.([]interface{}); ok { + // slices cannot be evaluated + result[key] = sliceValue + return + } valueString := types.ToString(value) combined := generators.MergeMaps(values, result) if value, ok := combined[key]; ok { @@ -76,12 +81,26 @@ func (variables *Variable) Evaluate(values map[string]interface{}) map[string]in return result } +// GetAll returns all variables as a map +func (variables *Variable) GetAll() map[string]interface{} { + result := make(map[string]interface{}, variables.Len()) + variables.ForEach(func(key string, value interface{}) { + result[key] = value + }) + return result +} + // EvaluateWithInteractsh returns evaluation results of variables with interactsh func (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) { result := make(map[string]interface{}, variables.Len()) var interactURLs []string variables.ForEach(func(key string, value interface{}) { + if sliceValue, ok := value.([]interface{}); ok { + // slices cannot be evaluated + result[key] = sliceValue + return + } valueString := types.ToString(value) if strings.Contains(valueString, "interactsh-url") { valueString, interactURLs = interact.Replace(valueString, interactURLs) diff --git a/v2/pkg/protocols/dns/dns.go b/v2/pkg/protocols/dns/dns.go index 1cc8302e..23242705 100644 --- a/v2/pkg/protocols/dns/dns.go +++ b/v2/pkg/protocols/dns/dns.go @@ -172,7 +172,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } if len(request.Payloads) > 0 { - request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType) + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options) if err != nil { return errors.Wrap(err, "could not parse payloads") } diff --git a/v2/pkg/protocols/dns/request.go b/v2/pkg/protocols/dns/request.go index 37cec212..a132ad3e 100644 --- a/v2/pkg/protocols/dns/request.go +++ b/v2/pkg/protocols/dns/request.go @@ -53,7 +53,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, // optionvars are vars passed from CLI or env variables optionVars := generators.BuildPayloadFromOptions(request.options.Options) // merge with metadata (eg. from workflow context) - vars = generators.MergeMaps(vars, metadata, optionVars) + vars = generators.MergeMaps(vars, metadata, optionVars, request.options.GetTemplateCtx(input.MetaInput).GetAll()) variablesMap := request.options.Variables.Evaluate(vars) vars = generators.MergeMaps(vars, variablesMap, request.options.Constants) @@ -66,21 +66,21 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, break } value = generators.MergeMaps(vars, value) - if err := request.execute(domain, metadata, previous, value, callback); err != nil { + if err := request.execute(input, domain, metadata, previous, value, callback); err != nil { return err } } } else { value := maps.Clone(vars) - return request.execute(domain, metadata, previous, value, callback) + return request.execute(input, domain, metadata, previous, value, callback) } return nil } -func (request *Request) execute(domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error { +func (request *Request) execute(input *contextargs.Context, domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error { if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(vars)) + gologger.Debug().Msgf("DNS Protocol request variables: \n%s\n", vardump.DumpVariables(vars)) } // Compile each request for the template based on the URL @@ -149,12 +149,17 @@ func (request *Request) execute(domain string, metadata, previous output.Interna // Create the output event outputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData) + // expose response variables in proto_var format + // this is no-op if the template is not a multi protocol template + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) for k, v := range previous { outputEvent[k] = v } for k, v := range vars { outputEvent[k] = v } + // add variables from template context before matching/extraction + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) event := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse) dumpResponse(event, request, request.options, response.String(), question) diff --git a/v2/pkg/protocols/file/request.go b/v2/pkg/protocols/file/request.go index 8d2ff70f..324b5a19 100644 --- a/v2/pkg/protocols/file/request.go +++ b/v2/pkg/protocols/file/request.go @@ -19,6 +19,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" @@ -63,7 +64,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, // every new file in the compressed multi-file archive counts 1 request.options.Progress.AddToTotal(1) archiveFileName := filepath.Join(filePath, file.Name()) - event, fileMatches, err := request.processReader(file.ReadCloser, archiveFileName, input.MetaInput.Input, file.Size(), previous) + event, fileMatches, err := request.processReader(file.ReadCloser, archiveFileName, input, file.Size(), previous) if err != nil { if errors.Is(err, errEmptyResult) { // no matches but one file elaborated @@ -116,7 +117,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, _ = tmpFileOut.Sync() // rewind the file _, _ = tmpFileOut.Seek(0, 0) - event, fileMatches, err := request.processReader(tmpFileOut, filePath, input.MetaInput.Input, fileStat.Size(), previous) + event, fileMatches, err := request.processReader(tmpFileOut, filePath, input, fileStat.Size(), previous) if err != nil { if errors.Is(err, errEmptyResult) { // no matches but one file elaborated @@ -136,7 +137,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, default: // normal file - increments the counter by 1 request.options.Progress.AddToTotal(1) - event, fileMatches, err := request.processFile(filePath, input.MetaInput.Input, previous) + event, fileMatches, err := request.processFile(filePath, input, previous) if err != nil { if errors.Is(err, errEmptyResult) { // no matches but one file elaborated @@ -165,7 +166,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, return nil } -func (request *Request) processFile(filePath, input string, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) { +func (request *Request) processFile(filePath string, input *contextargs.Context, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) { file, err := os.Open(filePath) if err != nil { return nil, nil, errors.Errorf("Could not open file path %s: %s\n", filePath, err) @@ -184,7 +185,7 @@ func (request *Request) processFile(filePath, input string, previousInternalEven return request.processReader(file, filePath, input, stat.Size(), previousInternalEvent) } -func (request *Request) processReader(reader io.Reader, filePath, input string, totalBytes int64, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) { +func (request *Request) processReader(reader io.Reader, filePath string, input *contextargs.Context, totalBytes int64, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) { fileReader := io.LimitReader(reader, request.maxSize) fileMatches, opResult := request.findMatchesWithReader(fileReader, input, filePath, totalBytes, previousInternalEvent) if opResult == nil && len(fileMatches) == 0 { @@ -192,10 +193,10 @@ func (request *Request) processReader(reader io.Reader, filePath, input string, } // build event structure to interface with internal logic - return request.buildEvent(input, filePath, fileMatches, opResult, previousInternalEvent), fileMatches, nil + return request.buildEvent(input.MetaInput.Input, filePath, fileMatches, opResult, previousInternalEvent), fileMatches, nil } -func (request *Request) findMatchesWithReader(reader io.Reader, input, filePath string, totalBytes int64, previous output.InternalEvent) ([]FileMatch, *operators.Result) { +func (request *Request) findMatchesWithReader(reader io.Reader, input *contextargs.Context, filePath string, totalBytes int64, previous output.InternalEvent) ([]FileMatch, *operators.Result) { var bytesCount, linesCount, wordsCount int isResponseDebug := request.options.Options.Debug || request.options.Options.DebugResponse totalBytesString := units.BytesSize(float64(totalBytes)) @@ -242,10 +243,12 @@ func (request *Request) findMatchesWithReader(reader io.Reader, input, filePath processedBytes := units.BytesSize(float64(currentBytes)) gologger.Verbose().Msgf("[%s] Processing file %s chunk %s/%s", request.options.TemplateID, filePath, processedBytes, totalBytesString) - dslMap := request.responseToDSLMap(lineContent, input, filePath) + dslMap := request.responseToDSLMap(lineContent, input.MetaInput.Input, filePath) for k, v := range previous { dslMap[k] = v } + // add template context variables to DSL map + dslMap = generators.MergeMaps(dslMap, request.options.GetTemplateCtx(input.MetaInput).GetAll()) discardEvent := eventcreator.CreateEvent(request, dslMap, isResponseDebug) newOpResult := discardEvent.OperatorsResult if newOpResult != nil { diff --git a/v2/pkg/protocols/headless/engine/page_actions.go b/v2/pkg/protocols/headless/engine/page_actions.go index 1b3cee29..3cd9cfd0 100644 --- a/v2/pkg/protocols/headless/engine/page_actions.go +++ b/v2/pkg/protocols/headless/engine/page_actions.go @@ -265,7 +265,7 @@ func (p *Page) NavigateURL(action *Action, out map[string]string, allvars map[st allvars = generators.MergeMaps(allvars, defaultReqVars) if vardump.EnableVarDump { - gologger.Debug().Msgf("Final Protocol request variables: \n%s\n", vardump.DumpVariables(allvars)) + gologger.Debug().Msgf("Headless Protocol request variables: \n%s\n", vardump.DumpVariables(allvars)) } // Evaluate the target url with all variables diff --git a/v2/pkg/protocols/headless/headless.go b/v2/pkg/protocols/headless/headless.go index 0d1d09e5..32dc1af8 100644 --- a/v2/pkg/protocols/headless/headless.go +++ b/v2/pkg/protocols/headless/headless.go @@ -91,6 +91,8 @@ func (request *Request) GetID() string { // Compile compiles the protocol request for further execution. func (request *Request) Compile(options *protocols.ExecutorOptions) error { + request.options = options + // TODO: logic similar to network + http => probably can be refactored // Resolve payload paths from vars if they exists for name, payload := range options.Options.Vars.AsMap() { @@ -106,7 +108,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { if len(request.Payloads) > 0 { var err error - request.generator, err = generators.New(request.Payloads, request.AttackType.Value, options.TemplatePath, options.Options.AllowLocalFileAccess, options.Catalog, options.Options.AttackType) + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, options.TemplatePath, options.Catalog, options.Options.AttackType, request.options.Options) if err != nil { return errors.Wrap(err, "could not parse payloads") } @@ -136,7 +138,6 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } request.CompiledOperators = compiled } - request.options = options if len(request.Fuzzing) > 0 { for _, rule := range request.Fuzzing { diff --git a/v2/pkg/protocols/headless/request.go b/v2/pkg/protocols/headless/request.go index ac6f3c93..93ece953 100644 --- a/v2/pkg/protocols/headless/request.go +++ b/v2/pkg/protocols/headless/request.go @@ -45,7 +45,8 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, vars := protocolutils.GenerateVariablesWithContextArgs(input, false) payloads := generators.BuildPayloadFromOptions(request.options.Options) - values := generators.MergeMaps(vars, metadata, payloads) + // add templatecontext variables to varMap + values := generators.MergeMaps(vars, metadata, payloads, request.options.GetTemplateCtx(input.MetaInput).GetAll()) variablesMap := request.options.Variables.Evaluate(values) payloads = generators.MergeMaps(variablesMap, payloads, request.options.Constants) @@ -95,7 +96,7 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p defer instance.Close() if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloads)) + gologger.Debug().Msgf("Headless Protocol request variables: \n%s\n", vardump.DumpVariables(payloads)) } instance.SetInteractsh(request.options.Interactsh) @@ -156,7 +157,10 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p responseBody, _ = html.HTML() } - outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, navigatedURL, page.DumpHistory()) + outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, input.MetaInput.Input, page.DumpHistory()) + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) for k, v := range out { outputEvent[k] = v } diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index c121b722..c3cf3997 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -73,6 +73,10 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, // value of `reqData` depends on the type of request specified in template // 1. If request is raw request = reqData contains raw request (i.e http request dump) // 2. If request is Normal ( simply put not a raw request) (Ex: with placeholders `path`) = reqData contains relative path + + // add template context values to dynamicValues (this takes care of self-contained and other types of requests) + // Note: `iterate-all` and flow are mutually exclusive. flow uses templateCtx and iterate-all uses dynamicValues + dynamicValues = generators.MergeMaps(dynamicValues, r.request.options.GetTemplateCtx(input.MetaInput).GetAll()) if r.request.SelfContained { return r.makeSelfContainedRequest(ctx, reqData, payloads, dynamicValues) } @@ -85,7 +89,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, } } else { for payloadName, payloadValue := range payloads { - payloads[payloadName] = types.ToString(payloadValue) + payloads[payloadName] = types.ToStringNSlice(payloadValue) } } @@ -130,7 +134,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, finalVars := generators.MergeMaps(allVars, payloads) if vardump.EnableVarDump { - gologger.Debug().Msgf("Final Protocol request variables: \n%s\n", vardump.DumpVariables(finalVars)) + gologger.Debug().Msgf("HTTP Protocol request variables: \n%s\n", vardump.DumpVariables(finalVars)) } // Note: If possible any changes to current logic (i.e evaluate -> then parse URL) diff --git a/v2/pkg/protocols/http/http.go b/v2/pkg/protocols/http/http.go index f674d9c2..4dfda24c 100644 --- a/v2/pkg/protocols/http/http.go +++ b/v2/pkg/protocols/http/http.go @@ -191,6 +191,7 @@ type Request struct { SkipVariablesCheck bool `yaml:"skip-variables-check,omitempty" json:"skip-variables-check,omitempty" jsonschema:"title=skip variable checks,description=Skips the check for unresolved variables in request"` // description: | // IterateAll iterates all the values extracted from internal extractors + // Deprecated: Use flow instead . iterate-all will be removed in future releases IterateAll bool `yaml:"iterate-all,omitempty" json:"iterate-all,omitempty" jsonschema:"title=iterate all the values,description=Iterates all the values extracted from internal extractors"` // description: | // DigestAuthUsername specifies the username for digest authentication @@ -356,7 +357,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } if len(request.Payloads) > 0 { - request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType) + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options) if err != nil { return errors.Wrap(err, "could not parse payloads") } diff --git a/v2/pkg/protocols/http/raw/raw.go b/v2/pkg/protocols/http/raw/raw.go index 2db22c5a..60471b61 100644 --- a/v2/pkg/protocols/http/raw/raw.go +++ b/v2/pkg/protocols/http/raw/raw.go @@ -104,7 +104,10 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b if _, ok := rawrequest.Headers["Host"]; !ok { rawrequest.Headers["Host"] = inputURL.Host } - rawrequest.FullURL = fmt.Sprintf("%s://%s%s", inputURL.Scheme, strings.TrimSpace(inputURL.Host), rawrequest.Path) + cloned := inputURL.Clone() + cloned.Path = "" + _ = cloned.MergePath(rawrequest.Path, true) + rawrequest.FullURL = cloned.String() } return rawrequest, nil diff --git a/v2/pkg/protocols/http/request.go b/v2/pkg/protocols/http/request.go index 3857bd9f..0dc3c542 100644 --- a/v2/pkg/protocols/http/request.go +++ b/v2/pkg/protocols/http/request.go @@ -767,6 +767,9 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ finalEvent := make(output.InternalEvent) outputEvent := request.responseToDSLMap(response.resp, input.MetaInput.Input, matchedURL, tostring.UnsafeToString(dumpedRequest), tostring.UnsafeToString(response.fullResponse), tostring.UnsafeToString(response.body), tostring.UnsafeToString(response.headers), duration, generatedRequest.meta) + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) if i := strings.LastIndex(hostname, ":"); i != -1 { hostname = hostname[:i] } diff --git a/v2/pkg/protocols/http/request_generator_test.go b/v2/pkg/protocols/http/request_generator_test.go index 89447b99..c57291d5 100644 --- a/v2/pkg/protocols/http/request_generator_test.go +++ b/v2/pkg/protocols/http/request_generator_test.go @@ -7,6 +7,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v2/pkg/types" ) func TestRequestGeneratorPaths(t *testing.T) { @@ -34,7 +35,7 @@ func TestRequestGeneratorClusterBombSingle(t *testing.T) { Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`}, } catalogInstance := disk.NewCatalog("") - req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", false, catalogInstance, "") + req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", catalogInstance, "", types.DefaultOptions()) require.Nil(t, err, "could not create generator") generator := req.newGenerator(false) @@ -58,7 +59,7 @@ func TestRequestGeneratorClusterBombMultipleRaw(t *testing.T) { Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`, `GET /{{username}}@{{password}} HTTP/1.1`}, } catalogInstance := disk.NewCatalog("") - req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", false, catalogInstance, "") + req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", catalogInstance, "", types.DefaultOptions()) require.Nil(t, err, "could not create generator") generator := req.newGenerator(false) diff --git a/v2/pkg/protocols/javascript/js.go b/v2/pkg/protocols/javascript/js.go new file mode 100644 index 00000000..73320b49 --- /dev/null +++ b/v2/pkg/protocols/javascript/js.go @@ -0,0 +1,659 @@ +package javascript + +import ( + "bytes" + "context" + "fmt" + "net" + "strings" + "sync/atomic" + "time" + + "github.com/alecthomas/chroma/quick" + "github.com/ditashi/jsbeautifier-go/jsbeautifier" + "github.com/dop251/goja" + "github.com/pkg/errors" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/js/compiler" + "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" + "github.com/projectdiscovery/nuclei/v2/pkg/model" + "github.com/projectdiscovery/nuclei/v2/pkg/operators" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" + templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + errorutil "github.com/projectdiscovery/utils/errors" + urlutil "github.com/projectdiscovery/utils/url" + "github.com/remeh/sizedwaitgroup" +) + +// Request is a request for the javascript protocol +type Request struct { + // Operators for the current request go here. + operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"` + CompiledOperators *operators.Operators `yaml:"-" json:"-"` + + // description: | + // ID is request id in that protocol + ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID is the optional ID of the Request"` + + // description: | + // Init is javascript code to execute after compiling template and before executing it on any target + // This is helpful for preparing payloads or other setup that maybe required for exploits + Init string `yaml:"init,omitempty" json:"init,omitempty" jsonschema:"title=init javascript code,description=Init is the javascript code to execute after compiling template"` + + // description: | + // PreCondition is a condition which is evaluated before sending the request. + PreCondition string `yaml:"pre-condition,omitempty" json:"pre-condition,omitempty" jsonschema:"title=pre-condition for the request,description=PreCondition is a condition which is evaluated before sending the request"` + + // description: | + // Args contains the arguments to pass to the javascript code. + Args map[string]interface{} `yaml:"args,omitempty" json:"args,omitempty"` + // description: | + // Code contains code to execute for the javascript request. + Code string `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code to execute in javascript,description=Executes inline javascript code for the request"` + + // description: | + // StopAtFirstMatch stops processing the request at first match. + StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"` + // description: | + // Attack is the type of payload combinations to perform. + // + // Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates + // permutations and combinations for all payloads. + AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"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"` + // description: | + // Payload concurreny i.e threads for sending requests. + // examples: + // - name: Send requests using 10 concurrent threads + // value: 10 + Threads int `yaml:"threads,omitempty" json:"threads,omitempty" jsonschema:"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling"` + // description: | + // Payloads contains any payloads for the current request. + // + // Payloads support both key-values combinations where a list + // of payloads is provided, or optionally a single file can also + // be provided as payload which will be read on run-time. + Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the webosocket request,description=Payloads contains any payloads for the current request"` + + generator *generators.PayloadGenerator + + // cache any variables that may be needed for operation. + options *protocols.ExecutorOptions `yaml:"-" json:"-"` +} + +// Compile compiles the request generators preparing any requests possible. +func (request *Request) Compile(options *protocols.ExecutorOptions) error { + request.options = options + + var err error + if len(request.Payloads) > 0 { + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options) + if err != nil { + return errors.Wrap(err, "could not parse payloads") + } + } + + if len(request.Matchers) > 0 || len(request.Extractors) > 0 { + compiled := &request.Operators + compiled.ExcludeMatchers = options.ExcludeMatchers + compiled.TemplateID = options.TemplateID + for _, matcher := range compiled.Matchers { + if matcher.Part == "" && matcher.Type.MatcherType != matchers.DSLMatcher { + matcher.Part = "response" + } + } + for _, extractor := range compiled.Extractors { + if extractor.Part == "" { + extractor.Part = "response" + } + } + if err := compiled.Compile(); err != nil { + return errorutil.NewWithTag(request.TemplateID, "could not compile operators got %v", err) + } + request.CompiledOperators = compiled + } + + // "Port" is a special variable and it should not contains any dsl expressions + if strings.Contains(request.getPort(), "{{") { + return errorutil.NewWithTag(request.TemplateID, "'Port' variable cannot contain any dsl expressions") + } + + if request.Init != "" { + // execute init code if any + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("[%s] Executing Template Init\n", request.TemplateID) + var highlightFormatter = "terminal256" + if request.options.Options.NoColor { + highlightFormatter = "text" + } + var buff bytes.Buffer + _ = quick.Highlight(&buff, beautifyJavascript(request.Init), "javascript", highlightFormatter, "monokai") + prettyPrint(request.TemplateID, buff.String()) + } + + opts := &compiler.ExecuteOptions{} + // register 'export' function to export variables from init code + // these are saved in args and are available in pre-condition and request code + opts.Callback = func(runtime *goja.Runtime) error { + err := gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "set", + Signatures: []string{ + "set(string, interface{})", + }, + Description: "set variable from init code. this function is available in init code block only", + FuncDecl: func(varname string, value any) error { + if varname == "" { + return fmt.Errorf("variable name cannot be empty") + } + if value == nil { + return fmt.Errorf("variable value cannot be empty") + } + if request.Args == nil { + request.Args = make(map[string]interface{}) + } + request.Args[varname] = value + return nil + }, + }) + if err != nil { + return err + } + + return gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{ + Name: "updatePayload", + Signatures: []string{ + "updatePayload(string, interface{})", + }, + Description: "update/override any payload from init code. this function is available in init code block only", + FuncDecl: func(varname string, Value any) error { + if request.Payloads == nil { + request.Payloads = make(map[string]interface{}) + } + if request.generator != nil { + request.Payloads[varname] = Value + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options) + if err != nil { + return err + } + } else { + return fmt.Errorf("payloads not defined and cannot be updated") + } + return nil + }, + }) + } + + args := compiler.NewExecuteArgs() + allVars := generators.MergeMaps(options.Variables.GetAll(), options.Options.Vars.AsMap(), request.options.Constants) + // proceed with whatever args we have + args.Args, _ = request.evaluateArgs(allVars, options, true) + + result, err := request.options.JsCompiler.ExecuteWithOptions(request.Init, args, opts) + if err != nil { + return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) + } + if types.ToString(result["error"]) != "" { + gologger.Warning().Msgf("[%s] Init failed with error %v\n", request.TemplateID, result["error"]) + return nil + } else { + if request.options.Options.Debug || request.options.Options.DebugResponse { + gologger.Debug().Msgf("[%s] Init executed successfully\n", request.TemplateID) + gologger.Debug().Msgf("[%s] Init result: %v\n", request.TemplateID, result["response"]) + } + } + } + + return nil +} + +// Options returns executer options for http request +func (r *Request) Options() *protocols.ExecutorOptions { + return r.options +} + +// Requests returns the total number of requests the rule will perform +func (request *Request) Requests() int { + pre_conditions := 0 + if request.PreCondition != "" { + pre_conditions = 1 + } + if request.generator != nil { + payloadRequests := request.generator.NewIterator().Total() + return payloadRequests + pre_conditions + } + return 1 + pre_conditions +} + +// GetID returns the ID for the request if any. +func (request *Request) GetID() string { + return request.ID +} + +// ExecuteWithResults executes the protocol requests and returns results instead of writing them. +func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { + + input := target.Clone() + // use network port updates input with new port requested in template file + // and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc + // idea is to reduce redundant dials to http ports + if err := input.UseNetworkPort(request.getPort(), request.getExcludePorts()); err != nil { + gologger.Debug().Msgf("Could not network port from constants: %s\n", err) + } + + hostPort, err := getAddress(input.MetaInput.Input) + if err != nil { + request.options.Progress.IncrementFailedRequestsBy(1) + return err + } + hostname, port, _ := net.SplitHostPort(hostPort) + if hostname == "" { + hostname = hostPort + } + + requestOptions := request.options + templateCtx := request.options.GetTemplateCtx(input.MetaInput) + + payloadValues := generators.BuildPayloadFromOptions(request.options.Options) + for k, v := range dynamicValues { + payloadValues[k] = v + } + + payloadValues["Hostname"] = hostPort + payloadValues["Host"] = hostname + payloadValues["Port"] = port + + hostnameVariables := protocolutils.GenerateDNSVariables(hostname) + values := generators.MergeMaps(payloadValues, hostnameVariables, request.options.Constants, templateCtx.GetAll()) + variablesMap := request.options.Variables.Evaluate(values) + payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants, hostnameVariables) + // export all variables to template context + templateCtx.Merge(payloadValues) + + if vardump.EnableVarDump { + gologger.Debug().Msgf("Javascript Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) + } + + if request.PreCondition != "" { + payloads := generators.MergeMaps(payloadValues, previous) + + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("[%s] Executing Precondition for request\n", request.TemplateID) + var highlightFormatter = "terminal256" + if requestOptions.Options.NoColor { + highlightFormatter = "text" + } + var buff bytes.Buffer + _ = quick.Highlight(&buff, beautifyJavascript(request.PreCondition), "javascript", highlightFormatter, "monokai") + prettyPrint(request.TemplateID, buff.String()) + } + + argsCopy, err := request.getArgsCopy(input, payloads, requestOptions, true) + if err != nil { + return err + } + argsCopy.TemplateCtx = templateCtx.GetAll() + + result, err := request.options.JsCompiler.ExecuteWithOptions(request.PreCondition, argsCopy, nil) + if err != nil { + return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) + } + if !result.GetSuccess() || types.ToString(result["error"]) != "" { + gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition) + request.options.Progress.IncrementFailedRequestsBy(1) + return nil + } + if request.options.Options.Debug || request.options.Options.DebugRequests { + request.options.Progress.IncrementRequests() + gologger.Debug().Msgf("[%s] Precondition for request was satisfied\n", request.TemplateID) + } + } + + if request.generator != nil && request.Threads > 1 { + request.executeRequestParallel(context.Background(), hostPort, hostname, input, payloadValues, callback) + return nil + } + + var gotMatches bool + if request.generator != nil { + iterator := request.generator.NewIterator() + + for { + value, ok := iterator.Value() + if !ok { + return nil + } + + if err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) { + if result.OperatorsResult != nil && result.OperatorsResult.Matched { + gotMatches = true + request.options.Progress.IncrementMatched() + } + callback(result) + }, requestOptions); err != nil { + _ = err + // Review: should we log error here? + // it is technically not error as it is expected to fail + // gologger.Warning().Msgf("Could not execute request: %s\n", err) + // do not return even if error occured + } + // If this was a match, and we want to stop at first match, skip all further requests. + shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch + if shouldStopAtFirstMatch && gotMatches { + return nil + } + } + } + return request.executeRequestWithPayloads(hostPort, input, hostname, nil, payloadValues, callback, requestOptions) +} + +func (request *Request) executeRequestParallel(ctxParent context.Context, hostPort, hostname string, input *contextargs.Context, payloadValues map[string]interface{}, callback protocols.OutputEventCallback) { + threads := request.Threads + if threads == 0 { + threads = 1 + } + ctx, cancel := context.WithCancel(ctxParent) + defer cancel() + requestOptions := request.options + gotmatches := &atomic.Bool{} + + sg := sizedwaitgroup.New(threads) + if request.generator != nil { + iterator := request.generator.NewIterator() + for { + value, ok := iterator.Value() + if !ok { + break + } + sg.Add() + go func() { + defer sg.Done() + if ctx.Err() != nil { + // work already done exit + return + } + shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch + if err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) { + if result.OperatorsResult != nil && result.OperatorsResult.Matched { + gotmatches.Store(true) + } + callback(result) + }, requestOptions); err != nil { + _ = err + // Review: should we log error here? + // it is technically not error as it is expected to fail + // gologger.Warning().Msgf("Could not execute request: %s\n", err) + // do not return even if error occured + } + // If this was a match, and we want to stop at first match, skip all further requests. + + if shouldStopAtFirstMatch && gotmatches.Load() { + cancel() + return + } + }() + } + } + sg.Wait() + if gotmatches.Load() { + request.options.Progress.IncrementMatched() + } +} + +func (request *Request) executeRequestWithPayloads(hostPort string, input *contextargs.Context, hostname string, payload map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback, requestOptions *protocols.ExecutorOptions) error { + payloadValues := generators.MergeMaps(payload, previous) + argsCopy, err := request.getArgsCopy(input, payloadValues, requestOptions, false) + if err != nil { + return err + } + argsCopy.TemplateCtx = request.options.GetTemplateCtx(input.MetaInput).GetAll() + + var requestData = []byte(request.Code) + var interactshURLs []string + if request.options.Interactsh != nil { + var transformedData string + transformedData, interactshURLs = request.options.Interactsh.Replace(string(request.Code), []string{}) + requestData = []byte(transformedData) + } + + results, err := request.options.JsCompiler.ExecuteWithOptions(string(requestData), argsCopy, &compiler.ExecuteOptions{ + Pool: false, + }) + if err != nil { + // shouldn't fail even if it returned error instead create a failure event + results = compiler.ExecuteResult{"success": false, "error": err.Error()} + } + request.options.Progress.IncrementRequests() + + requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err) + gologger.Verbose().Msgf("[%s] Sent Javascript request to %s", request.TemplateID, hostPort) + + if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse { + msg := fmt.Sprintf("[%s] Dumped Javascript request for %s:\nVariables:\n %v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(argsCopy.Args)) + + if requestOptions.Options.Debug || requestOptions.Options.DebugRequests { + gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg) + var highlightFormatter = "terminal256" + if requestOptions.Options.NoColor { + highlightFormatter = "text" + } + var buff bytes.Buffer + _ = quick.Highlight(&buff, beautifyJavascript(request.Code), "javascript", highlightFormatter, "monokai") + prettyPrint(request.TemplateID, buff.String()) + } + if requestOptions.Options.StoreResponse { + request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg) + } + } + + data := make(map[string]interface{}) + for k, v := range payloadValues { + data[k] = v + } + data["type"] = request.Type().String() + for k, v := range results { + data[k] = v + } + data["request"] = beautifyJavascript(request.Code) + data["host"] = input.MetaInput.Input + data["matched"] = hostPort + data["template-path"] = requestOptions.TemplatePath + data["template-id"] = requestOptions.TemplateID + data["template-info"] = requestOptions.TemplateInfo + if request.StopAtFirstMatch || request.options.StopAtFirstMatch { + data["stop-at-first-match"] = true + } + + // add and get values from templatectx + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), data) + data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll()) + + if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse { + msg := fmt.Sprintf("[%s] Dumped Javascript response for %s:\n%v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(results)) + if requestOptions.Options.Debug || requestOptions.Options.DebugRequests { + gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg) + } + if requestOptions.Options.StoreResponse { + request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg) + } + } + + if _, ok := data["error"]; ok { + event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) { + wrappedEvent.OperatorsResult.PayloadValues = payload + }) + callback(event) + return err + } + + if request.options.Interactsh != nil { + request.options.Interactsh.MakePlaceholders(interactshURLs, data) + } + + var event *output.InternalWrappedEvent + if len(interactshURLs) == 0 { + event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) { + wrappedEvent.OperatorsResult.PayloadValues = payload + }) + callback(event) + } else if request.options.Interactsh != nil { + event = &output.InternalWrappedEvent{InternalEvent: data, UsesInteractsh: true} + request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{ + MakeResultFunc: request.MakeResultEvent, + Event: event, + Operators: request.CompiledOperators, + MatchFunc: request.Match, + ExtractFunc: request.Extract, + }) + } + return nil +} + +func (request *Request) getArgsCopy(input *contextargs.Context, payloadValues map[string]interface{}, requestOptions *protocols.ExecutorOptions, ignoreErrors bool) (*compiler.ExecuteArgs, error) { + // Template args from payloads + argsCopy, err := request.evaluateArgs(payloadValues, requestOptions, ignoreErrors) + if err != nil { + requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err) + requestOptions.Progress.IncrementFailedRequestsBy(1) + } + // "Port" is a special variable that is considered as network port + // and is conditional based on input port and default port specified in input + argsCopy["Port"] = input.Port() + + return &compiler.ExecuteArgs{Args: argsCopy}, nil +} + +// evaluateArgs evaluates arguments using available payload values and returns a copy of args +func (request *Request) evaluateArgs(payloadValues map[string]interface{}, requestOptions *protocols.ExecutorOptions, ignoreErrors bool) (map[string]interface{}, error) { + argsCopy := make(map[string]interface{}) +mainLoop: + for k, v := range request.Args { + if vVal, ok := v.(string); ok && strings.Contains(vVal, "{") { + finalAddress, dataErr := expressions.Evaluate(vVal, payloadValues) + if dataErr != nil { + return nil, errors.Wrap(dataErr, "could not evaluate template expressions") + } + if finalAddress == vVal && ignoreErrors { + argsCopy[k] = "" + continue mainLoop + } + argsCopy[k] = finalAddress + } else { + argsCopy[k] = v + } + } + return argsCopy, nil +} + +// RequestPartDefinitions contains a mapping of request part definitions and their +// description. Multiple definitions are separated by commas. +// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>. +var RequestPartDefinitions = map[string]string{ + "type": "Type is the type of request made", + "response": "Javascript protocol result response", + "host": "Host is the input to the template", + "matched": "Matched is the input which was matched upon", +} + +// getAddress returns the address of the host to make request to +func getAddress(toTest string) (string, error) { + urlx, err := urlutil.Parse(toTest) + if err != nil { + // use given input instead of url parsing failure + return toTest, nil + } + return urlx.Host, nil +} + +// Match performs matching operation for a matcher on model and returns: +// true and a list of matched snippets if the matcher type is supports it +// otherwise false and an empty string slice +func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) { + return protocols.MakeDefaultMatchFunc(data, matcher) +} + +// Extract performs extracting operation for an extractor on model and returns true or false. +func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} { + return protocols.MakeDefaultExtractFunc(data, matcher) +} + +// MakeResultEvent creates a result event from internal wrapped event +func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent { + return protocols.MakeDefaultResultEvent(request, wrapped) +} + +// GetCompiledOperators returns a list of the compiled operators +func (request *Request) GetCompiledOperators() []*operators.Operators { + return []*operators.Operators{request.CompiledOperators} +} + +// Type returns the type of the protocol request +func (request *Request) Type() templateTypes.ProtocolType { + return templateTypes.JavascriptProtocol +} + +func (request *Request) getPort() string { + for k, v := range request.Args { + if strings.EqualFold(k, "Port") { + return types.ToString(v) + } + } + return "" +} + +func (request *Request) getExcludePorts() string { + for k, v := range request.Args { + if strings.EqualFold(k, "exclude-ports") { + return types.ToString(v) + } + } + return "" +} + +func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { + data := &output.ResultEvent{ + TemplateID: types.ToString(wrapped.InternalEvent["template-id"]), + TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]), + Info: wrapped.InternalEvent["template-info"].(model.Info), + Type: types.ToString(wrapped.InternalEvent["type"]), + Host: types.ToString(wrapped.InternalEvent["host"]), + Matched: types.ToString(wrapped.InternalEvent["matched"]), + Metadata: wrapped.OperatorsResult.PayloadValues, + ExtractedResults: wrapped.OperatorsResult.OutputExtracts, + Timestamp: time.Now(), + MatcherStatus: true, + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["response"]), + IP: types.ToString(wrapped.InternalEvent["ip"]), + } + return data +} + +func beautifyJavascript(code string) string { + opts := jsbeautifier.DefaultOptions() + beautified, err := jsbeautifier.Beautify(&code, opts) + if err != nil { + return code + } + return beautified +} + +func prettyPrint(templateId string, buff string) { + lines := strings.Split(buff, "\n") + final := []string{} + for _, v := range lines { + if v != "" { + final = append(final, "\t"+v) + } + } + gologger.Debug().Msgf(" [%v] Javascript Code:\n\n%v\n\n", templateId, strings.Join(final, "\n")) +} diff --git a/v2/pkg/protocols/javascript/js_test.go b/v2/pkg/protocols/javascript/js_test.go new file mode 100644 index 00000000..a2216e36 --- /dev/null +++ b/v2/pkg/protocols/javascript/js_test.go @@ -0,0 +1,74 @@ +package javascript_test + +import ( + "context" + "log" + "testing" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/parsers" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" + "github.com/projectdiscovery/ratelimit" + "github.com/stretchr/testify/require" +) + +var ( + testcases = []string{ + "testcases/ms-sql-detect.yaml", + "testcases/redis-pass-brute.yaml", + "testcases/ssh-server-fingerprint.yaml", + } + executerOpts protocols.ExecutorOptions +) + +func setup() { + options := testutils.DefaultOptions + testutils.Init(options) + progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) + + executerOpts = protocols.ExecutorOptions{ + Output: testutils.NewMockOutputWriter(), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + } + workflowLoader, err := parsers.NewLoader(&executerOpts) + if err != nil { + log.Fatalf("Could not create workflow loader: %s\n", err) + } + executerOpts.WorkflowLoader = workflowLoader +} + +func TestCompile(t *testing.T) { + setup() + for index, tpl := range testcases { + // parse template + template, err := templates.Parse(tpl, nil, executerOpts) + require.Nilf(t, err, "failed to parse %v", tpl) + + // compile template + err = template.Executer.Compile() + require.Nilf(t, err, "failed to compile %v", tpl) + + switch index { + case 0: + // requests count should be 1 + require.Equal(t, 1, template.TotalRequests, "template : %v", tpl) + case 1: + // requests count should be 6 i.e 5 generator payloads + 1 precondition request + require.Equal(t, 5+1, template.TotalRequests, "template : %v", tpl) + case 2: + // requests count should be 1 + require.Equal(t, 1, template.TotalRequests, "template : %v", tpl) + } + } +} diff --git a/v2/pkg/protocols/javascript/testcases/ms-sql-detect.yaml b/v2/pkg/protocols/javascript/testcases/ms-sql-detect.yaml new file mode 100644 index 00000000..5865573a --- /dev/null +++ b/v2/pkg/protocols/javascript/testcases/ms-sql-detect.yaml @@ -0,0 +1,29 @@ +id: ms-sql-detect + +info: + name: microsoft sql server(mssql) detection + author: Ice3man543,tarunKoyalwar + severity: info + description: | + ms sql detection template + metadata: + shodan-query: "port:1433" + +javascript: + - code: | + var m = require("nuclei/mssql"); + var c = m.MSSQLClient(); + c.IsMssql(Host, Port); + + args: + Host: "{{Host}}" + Port: "1433" + + matchers: + - type: dsl + dsl: + - "response == true" + - "success == true" + condition: and + + diff --git a/v2/pkg/protocols/javascript/testcases/redis-pass-brute.yaml b/v2/pkg/protocols/javascript/testcases/redis-pass-brute.yaml new file mode 100644 index 00000000..d4d09230 --- /dev/null +++ b/v2/pkg/protocols/javascript/testcases/redis-pass-brute.yaml @@ -0,0 +1,43 @@ +id: redis-pass-brute +info: + name: redis password bruteforce + author: tarunKoyalwar + severity: high + description: | + This template bruteforces passwords for protected redis instances. + If redis is not protected with password. it is also matched + metadata: + shodan-query: product:"redis" + + +javascript: + - pre-condition: | + isPortOpen(Host,Port) + + code: | + var m = require("nuclei/redis"); + m.GetServerInfoAuth(Host,Port,Password); + + args: + Host: "{{Host}}" + Port: "6379" + Password: "{{passwords}}" + + payloads: + passwords: + - "" + - root + - password + - admin + - iamadmin + stop-at-first-match: true + + matchers-condition: and + matchers: + - type: word + words: + - "redis_version" + - type: word + negative: true + words: + - "redis_mode:sentinel" diff --git a/v2/pkg/protocols/javascript/testcases/ssh-server-fingerprint.yaml b/v2/pkg/protocols/javascript/testcases/ssh-server-fingerprint.yaml new file mode 100644 index 00000000..a5f4ff4c --- /dev/null +++ b/v2/pkg/protocols/javascript/testcases/ssh-server-fingerprint.yaml @@ -0,0 +1,24 @@ +id: ssh-server-fingerprint + +info: + name: Fingerprint SSH Server Software + author: Ice3man543,tarunKoyalwar + severity: info + + +javascript: + - code: | + var m = require("nuclei/ssh"); + var c = m.SSHClient(); + var response = c.ConnectSSHInfoMode(Host, Port); + to_json(response); + args: + Host: "{{Host}}" + Port: "22" + + extractors: + - type: json + name: server + json: + - '.ServerID.Raw' + part: response diff --git a/v2/pkg/protocols/network/network.go b/v2/pkg/protocols/network/network.go index 0a738f8e..9ac03f4c 100644 --- a/v2/pkg/protocols/network/network.go +++ b/v2/pkg/protocols/network/network.go @@ -192,7 +192,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } if len(request.Payloads) > 0 { - request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType) + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options) if err != nil { return errors.Wrap(err, "could not parse payloads") } diff --git a/v2/pkg/protocols/network/request.go b/v2/pkg/protocols/network/request.go index 758747bb..5f720beb 100644 --- a/v2/pkg/protocols/network/request.go +++ b/v2/pkg/protocols/network/request.go @@ -63,6 +63,8 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata return errors.Wrap(err, "could not get address from url") } variables := protocolutils.GenerateVariables(address, false, nil) + // add template ctx variables to varMap + variables = generators.MergeMaps(variables, request.options.GetTemplateCtx(input.MetaInput).GetAll()) variablesMap := request.options.Variables.Evaluate(variables) variables = generators.MergeMaps(variablesMap, variables, request.options.Constants) @@ -76,7 +78,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata } visitedAddresses.Set(actualAddress, struct{}{}) - if err := request.executeAddress(variables, actualAddress, address, input.MetaInput.Input, kv.tls, previous, callback); err != nil { + if err := request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, callback); err != nil { outputEvent := request.responseToDSLMap("", "", "", address, "") callback(&output.InternalWrappedEvent{InternalEvent: outputEvent}) gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err) @@ -87,7 +89,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata } // executeAddress executes the request for an address -func (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error { +func (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error { variables = generators.MergeMaps(variables, map[string]interface{}{"Hostname": address}) payloads := generators.BuildPayloadFromOptions(request.options.Options) @@ -120,7 +122,7 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA return nil } -func (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { +func (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { var ( hostname string conn net.Conn @@ -150,7 +152,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac interimValues := generators.MergeMaps(variables, payloads) if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues)) + gologger.Debug().Msgf("Network Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues)) } inputEvents := make(map[string]interface{}) @@ -285,7 +287,10 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac } response := responseBuilder.String() - outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress) + outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input.MetaInput.Input, actualAddress) + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) outputEvent["ip"] = request.dialer.GetDialedIP(hostname) if request.options.StopAtFirstMatch { outputEvent["stop-at-first-match"] = true diff --git a/v2/pkg/protocols/offlinehttp/request.go b/v2/pkg/protocols/offlinehttp/request.go index 2bcbeacc..a942288a 100644 --- a/v2/pkg/protocols/offlinehttp/request.go +++ b/v2/pkg/protocols/offlinehttp/request.go @@ -12,6 +12,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" @@ -86,6 +87,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata } outputEvent := request.responseToDSLMap(resp, data, data, data, tostring.UnsafeToString(dumpedResponse), tostring.UnsafeToString(body), utils.HeadersToString(resp.Header), 0, nil) + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), outputEvent) + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) outputEvent["ip"] = "" for k, v := range previous { outputEvent[k] = v diff --git a/v2/pkg/protocols/protocols.go b/v2/pkg/protocols/protocols.go index 9e0d2a6e..226f0937 100644 --- a/v2/pkg/protocols/protocols.go +++ b/v2/pkg/protocols/protocols.go @@ -1,12 +1,17 @@ package protocols import ( + "sync/atomic" + "github.com/projectdiscovery/ratelimit" + mapsutil "github.com/projectdiscovery/utils/maps" + stringsutil "github.com/projectdiscovery/utils/strings" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/nuclei/v2/pkg/catalog" "github.com/projectdiscovery/nuclei/v2/pkg/input" + "github.com/projectdiscovery/nuclei/v2/pkg/js/compiler" "github.com/projectdiscovery/nuclei/v2/pkg/model" "github.com/projectdiscovery/nuclei/v2/pkg/operators" "github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors" @@ -85,11 +90,90 @@ type ExecutorOptions struct { Colorizer aurora.Aurora WorkflowLoader model.WorkflowLoader ResumeCfg *types.ResumeCfg + // ProtocolType is the type of the template + ProtocolType templateTypes.ProtocolType + // Flow is execution flow for the template (written in javascript) + Flow string + // IsMultiProtocol is true if template has more than one protocol + IsMultiProtocol bool + // templateStore is a map which contains template context for each scan (i.e input * template-id pair) + templateCtxStore *mapsutil.SyncLockMap[string, *contextargs.Context] + // JsCompiler is abstracted javascript compiler which adds node modules and provides execution + // environment for javascript templates + JsCompiler *compiler.Compiler +} + +// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan) +func (e *ExecutorOptions) CreateTemplateCtxStore() { + e.templateCtxStore = &mapsutil.SyncLockMap[string, *contextargs.Context]{ + Map: make(map[string]*contextargs.Context), + ReadOnly: atomic.Bool{}, + } +} + +// RemoveTemplateCtx removes template context of given scan from store +func (e *ExecutorOptions) RemoveTemplateCtx(input *contextargs.MetaInput) { + scanId := input.GetScanHash(e.TemplateID) + if e.templateCtxStore != nil { + e.templateCtxStore.Delete(scanId) + } +} + +// GetTemplateCtx returns template context for given input +func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context { + scanId := input.GetScanHash(e.TemplateID) + templateCtx, ok := e.templateCtxStore.Get(scanId) + if !ok { + // if template context does not exist create new and add it to store and return it + templateCtx = contextargs.New() + _ = e.templateCtxStore.Set(scanId, templateCtx) + } + return templateCtx +} + +// AddTemplateVars adds vars to template context with given template type as prefix +// this method is no-op if template is not multi protocol +func (e *ExecutorOptions) AddTemplateVars(input *contextargs.MetaInput, reqType templateTypes.ProtocolType, reqID string, vars map[string]interface{}) { + // if we wan't to disable adding response variables and other variables to template context + // this is the statement that does it . template context is currently only enabled for + // multiprotocol and flow templates + if !e.IsMultiProtocol && e.Flow == "" { + // no-op if not multi protocol template or flow template + return + } + templateCtx := e.GetTemplateCtx(input) + for k, v := range vars { + if !stringsutil.EqualFoldAny(k, "template-id", "template-info", "template-path") { + if reqID != "" { + k = reqID + "_" + k + } else if reqType < templateTypes.InvalidProtocol { + k = reqType.String() + "_" + k + } + templateCtx.Set(k, v) + } + } +} + +// AddTemplateVar adds given var to template context with given template type as prefix +// this method is no-op if template is not multi protocol +func (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateType templateTypes.ProtocolType, reqID string, key string, value interface{}) { + if !e.IsMultiProtocol && e.Flow == "" { + // no-op if not multi protocol template or flow template + return + } + templateCtx := e.GetTemplateCtx(input) + if reqID != "" { + key = reqID + "_" + key + } else if templateType < templateTypes.InvalidProtocol { + key = templateType.String() + "_" + key + } + templateCtx.Set(key, value) } // Copy returns a copy of the executeroptions structure func (e ExecutorOptions) Copy() ExecutorOptions { copy := e + copy.CreateTemplateCtxStore() return copy } diff --git a/v2/pkg/protocols/ssl/ssl.go b/v2/pkg/protocols/ssl/ssl.go index 56972c01..a32e8491 100644 --- a/v2/pkg/protocols/ssl/ssl.go +++ b/v2/pkg/protocols/ssl/ssl.go @@ -24,7 +24,6 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/network/networkclientpool" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/types" @@ -42,6 +41,9 @@ type Request struct { operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"` CompiledOperators *operators.Operators `yaml:"-" json:"-"` + // ID is the optional id of the request + ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID of the request"` + // description: | // Address contains address for the request Address string `yaml:"address,omitempty" json:"address,omitempty" jsonschema:"title=address for the ssl request,description=Address contains address for the request"` @@ -130,6 +132,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { Fastdialer: client, ClientHello: true, ServerHello: true, + DisplayDns: true, } tlsxService, err := tlsx.New(tlsxOptions) @@ -184,12 +187,13 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa payloadValues["Port"] = port hostnameVariables := protocolutils.GenerateDNSVariables(hostname) - values := generators.MergeMaps(payloadValues, hostnameVariables) + // add template context variables to varMap + values := generators.MergeMaps(payloadValues, hostnameVariables, request.options.GetTemplateCtx(input.MetaInput).GetAll()) variablesMap := request.options.Variables.Evaluate(values) payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants) if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) + gologger.Debug().Msgf("SSL Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) } finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues) @@ -219,7 +223,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa } requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err) - gologger.Verbose().Msgf("Sent SSL request to %s", hostPort) + gologger.Verbose().Msgf("[%s] Sent SSL request to %s", request.options.TemplateID, hostPort) if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse { msg := fmt.Sprintf("[%s] Dumped SSL request for %s", requestOptions.TemplateID, input.MetaInput.Input) @@ -263,10 +267,11 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa // if field is not exported f.IsZero() , f.Value() will panic continue } - tag := utils.CleanStructFieldJSONTag(f.Tag("json")) + tag := protocolutils.CleanStructFieldJSONTag(f.Tag("json")) if tag == "" || f.IsZero() { continue } + request.options.AddTemplateVar(input.MetaInput, request.Type(), request.ID, tag, f.Value()) data[tag] = f.Value() } @@ -281,13 +286,16 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa // if field is not exported f.IsZero() , f.Value() will panic continue } - tag := utils.CleanStructFieldJSONTag(f.Tag("json")) + tag := protocolutils.CleanStructFieldJSONTag(f.Tag("json")) if tag == "" || f.IsZero() { continue } + request.options.AddTemplateVar(input.MetaInput, request.Type(), request.ID, tag, f.Value()) data[tag] = f.Value() } + // add response fields ^ to template context and merge templatectx variables to output event + data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll()) event := eventcreator.CreateEvent(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse) if requestOptions.Options.Debug || requestOptions.Options.DebugResponse || requestOptions.Options.StoreResponse { msg := fmt.Sprintf("[%s] Dumped SSL response for %s", requestOptions.TemplateID, input.MetaInput.Input) diff --git a/v2/pkg/protocols/ssl/ssl_test.go b/v2/pkg/protocols/ssl/ssl_test.go index 77c03b22..4ba87b13 100644 --- a/v2/pkg/protocols/ssl/ssl_test.go +++ b/v2/pkg/protocols/ssl/ssl_test.go @@ -28,7 +28,7 @@ func TestSSLProtocol(t *testing.T) { require.Nil(t, err, "could not compile ssl request") var gotEvent output.InternalEvent - ctxArgs := contextargs.NewWithInput("google.com:443") + ctxArgs := contextargs.NewWithInput("scanme.sh:443") err = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) { gotEvent = event.InternalEvent }) @@ -37,6 +37,6 @@ func TestSSLProtocol(t *testing.T) { } func TestGetAddress(t *testing.T) { - address, _ := getAddress("https://google.com") - require.Equal(t, "google.com:443", address, "could not get correct address") + address, _ := getAddress("https://scanme.sh") + require.Equal(t, "scanme.sh:443", address, "could not get correct address") } diff --git a/v2/pkg/protocols/utils/variables.go b/v2/pkg/protocols/utils/variables.go index 92e562ce..230f1f15 100644 --- a/v2/pkg/protocols/utils/variables.go +++ b/v2/pkg/protocols/utils/variables.go @@ -149,7 +149,11 @@ func generateVariables(inputURL *urlutil.URL, removeTrailingSlash bool) map[stri case BaseURL: knownVariables[v] = parsed.String() case RootURL: - knownVariables[v] = fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host) + if parsed.Scheme != "" { + knownVariables[v] = fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host) + } else { + knownVariables[v] = parsed.Host + } case Hostname: knownVariables[v] = parsed.Host case Host: diff --git a/v2/pkg/protocols/websocket/websocket.go b/v2/pkg/protocols/websocket/websocket.go index ab51478a..ed2e7bf9 100644 --- a/v2/pkg/protocols/websocket/websocket.go +++ b/v2/pkg/protocols/websocket/websocket.go @@ -42,6 +42,8 @@ type Request struct { operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"` CompiledOperators *operators.Operators `yaml:"-" json:"-"` + // ID is the optional id of the request + ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID of the network request"` // description: | // Address contains address for the request Address string `yaml:"address,omitempty" json:"address,omitempty" jsonschema:"title=address for the websocket request,description=Address contains address for the request"` @@ -106,7 +108,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.dialer = client if len(request.Payloads) > 0 { - request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, options.Catalog, options.Options.AttackType) + request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, types.DefaultOptions()) if err != nil { return errors.Wrap(err, "could not parse payloads") } @@ -152,13 +154,13 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa if !ok { break } - if err := request.executeRequestWithPayloads(input.MetaInput.Input, hostname, value, previous, callback); err != nil { + if err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil { return err } } } else { value := make(map[string]interface{}) - if err := request.executeRequestWithPayloads(input.MetaInput.Input, hostname, value, previous, callback); err != nil { + if err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil { return err } } @@ -166,8 +168,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa } // ExecuteWithResults executes the protocol requests and returns results instead of writing them. -func (request *Request) executeRequestWithPayloads(input, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { +func (request *Request) executeRequestWithPayloads(target *contextargs.Context, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { header := http.Header{} + input := target.MetaInput.Input parsed, err := urlutil.Parse(input) if err != nil { @@ -175,7 +178,8 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam } defaultVars := protocolutils.GenerateVariables(parsed, false, nil) optionVars := generators.BuildPayloadFromOptions(request.options.Options) - variables := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues)) + // add templatecontext variables to varMap + variables := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues, request.options.GetTemplateCtx(target.MetaInput).GetAll())) payloadValues := generators.MergeMaps(variables, defaultVars, optionVars, dynamicValues, request.options.Constants) requestOptions := request.options @@ -204,7 +208,7 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam } if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) + gologger.Debug().Msgf("Websocket Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) } finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues) @@ -254,12 +258,6 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam gologger.Verbose().Msgf("Sent Websocket request to %s", input) data := make(map[string]interface{}) - for k, v := range previous { - data[k] = v - } - for k, v := range events { - data[k] = v - } data["type"] = request.Type().String() data["success"] = "true" @@ -269,6 +267,17 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam data["matched"] = addressToDial data["ip"] = request.dialer.GetDialedIP(hostname) + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(target.MetaInput, request.Type(), request.ID, data) + data = generators.MergeMaps(data, request.options.GetTemplateCtx(target.MetaInput).GetAll()) + + for k, v := range previous { + data[k] = v + } + for k, v := range events { + data[k] = v + } + event := eventcreator.CreateEventWithAdditionalOptions(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) { internalWrappedEvent.OperatorsResult.PayloadValues = payloadValues }) diff --git a/v2/pkg/protocols/whois/whois.go b/v2/pkg/protocols/whois/whois.go index 18c5b409..6ab686ff 100644 --- a/v2/pkg/protocols/whois/whois.go +++ b/v2/pkg/protocols/whois/whois.go @@ -34,6 +34,9 @@ type Request struct { operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"` CompiledOperators *operators.Operators `yaml:"-" json:"-"` + // ID is the optional id of the request + ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID of the network request"` + // description: | // Query contains query for the request Query string `yaml:"query,omitempty" json:"query,omitempty" jsonschema:"title=query for the WHOIS request,description=Query contains query for the request"` @@ -90,12 +93,13 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa // generate variables defaultVars := protocolutils.GenerateVariables(input.MetaInput.Input, false, nil) optionVars := generators.BuildPayloadFromOptions(request.options.Options) - vars := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues)) + // add templatectx variables to varMap + vars := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues, request.options.GetTemplateCtx(input.MetaInput).GetAll())) variables := generators.MergeMaps(vars, defaultVars, optionVars, dynamicValues, request.options.Constants) if vardump.EnableVarDump { - gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(variables)) + gologger.Debug().Msgf("Whois Protocol request variables: \n%s\n", vardump.DumpVariables(variables)) } // and replace placeholders @@ -132,6 +136,10 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa data["host"] = query data["response"] = jsonDataString + // add response fields to template context and merge templatectx variables to output event + request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, data) + data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll()) + event := eventcreator.CreateEvent(request, data, request.options.Options.Debug || request.options.Options.DebugResponse) if request.options.Options.Debug || request.options.Options.DebugResponse { gologger.Debug().Msgf("[%s] Dumped WHOIS response for %s", request.options.TemplateID, query) diff --git a/v2/pkg/reporting/dedupe/dedupe.go b/v2/pkg/reporting/dedupe/dedupe.go index a30de782..85a9200a 100644 --- a/v2/pkg/reporting/dedupe/dedupe.go +++ b/v2/pkg/reporting/dedupe/dedupe.go @@ -7,7 +7,6 @@ package dedupe import ( "crypto/sha1" "os" - "reflect" "unsafe" "github.com/syndtr/goleveldb/leveldb" @@ -119,7 +118,6 @@ func (s *Storage) Index(result *output.ResultEvent) (bool, error) { // // Reference - https://stackoverflow.com/questions/59209493/how-to-use-unsafe-get-a-byte-slice-from-a-string-without-memory-copy func unsafeToBytes(data string) []byte { - var buf = *(*[]byte)(unsafe.Pointer(&data)) - (*reflect.SliceHeader)(unsafe.Pointer(&buf)).Cap = len(data) - return buf + var buf = (*[]byte)(unsafe.Pointer(&data)) + return *buf } diff --git a/v2/pkg/templates/compile.go b/v2/pkg/templates/compile.go index df51d8ee..3fd4aa41 100644 --- a/v2/pkg/templates/compile.go +++ b/v2/pkg/templates/compile.go @@ -1,32 +1,50 @@ package templates import ( + "encoding/json" "fmt" "io" "reflect" + "sync" + "sync/atomic" + "github.com/logrusorgru/aurora" "github.com/pkg/errors" "gopkg.in/yaml.v2" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/js/compiler" "github.com/projectdiscovery/nuclei/v2/pkg/operators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/executer" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/offlinehttp" "github.com/projectdiscovery/nuclei/v2/pkg/templates/cache" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec" "github.com/projectdiscovery/nuclei/v2/pkg/utils" "github.com/projectdiscovery/retryablehttp-go" + errorutil "github.com/projectdiscovery/utils/errors" stringsutil "github.com/projectdiscovery/utils/strings" ) var ( ErrCreateTemplateExecutor = errors.New("cannot create template executer") ErrIncompatibleWithOfflineMatching = errors.New("template can't be used for offline matching") + parsedTemplatesCache *cache.Templates + // track how many templates are verfied and by which signer + SignatureStats = map[string]*atomic.Uint64{} ) -var parsedTemplatesCache *cache.Templates +const ( + Unsigned = "unsigned" +) func init() { parsedTemplatesCache = cache.New() + for _, verifier := range signer.DefaultTemplateVerifiers { + SignatureStats[verifier.Identifier()] = &atomic.Uint64{} + } + SignatureStats[Unsigned] = &atomic.Uint64{} } // Parse parses a yaml request template file @@ -57,7 +75,7 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Executo } defer reader.Close() options.TemplatePath = filePath - template, err := ParseTemplateFromReader(reader, preprocessor, options) + template, err := ParseTemplateFromReader(reader, preprocessor, options.Copy()) if err != nil { return nil, err } @@ -104,7 +122,9 @@ func (template *Template) Requests() int { len(template.Workflows) + len(template.RequestsSSL) + len(template.RequestsWebsocket) + - len(template.RequestsWHOIS) + len(template.RequestsWHOIS) + + len(template.RequestsCode) + + len(template.RequestsJavascript) } // compileProtocolRequests compiles all the protocol requests for the template @@ -121,31 +141,46 @@ func (template *Template) compileProtocolRequests(options protocols.ExecutorOpti var requests []protocols.Request - if len(template.RequestsDNS) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsDNS)...) + if template.hasMultipleRequests() { + // when multiple requests are present preserve the order of requests and protocols + // which is already done during unmarshalling + requests = template.RequestsQueue + if options.Flow == "" { + options.IsMultiProtocol = true + } + } else { + if len(template.RequestsDNS) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsDNS)...) + } + if len(template.RequestsFile) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsFile)...) + } + if len(template.RequestsNetwork) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...) + } + if len(template.RequestsHTTP) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...) + } + if len(template.RequestsHeadless) > 0 && options.Options.Headless { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...) + } + if len(template.RequestsSSL) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsSSL)...) + } + if len(template.RequestsWebsocket) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...) + } + if len(template.RequestsWHOIS) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...) + } + if len(template.RequestsCode) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsCode)...) + } + if len(template.RequestsJavascript) > 0 { + requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...) + } } - if len(template.RequestsFile) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsFile)...) - } - if len(template.RequestsNetwork) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...) - } - if len(template.RequestsHTTP) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...) - } - if len(template.RequestsHeadless) > 0 && options.Options.Headless { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...) - } - if len(template.RequestsSSL) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsSSL)...) - } - if len(template.RequestsWebsocket) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...) - } - if len(template.RequestsWHOIS) > 0 { - requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...) - } - template.Executer = executer.NewExecuter(requests, &options) + template.Executer = tmplexec.NewTemplateExecuter(requests, &options) return nil } @@ -190,7 +225,7 @@ mainLoop: } if len(operatorsList) > 0 { options.Operators = operatorsList - template.Executer = executer.NewExecuter([]protocols.Request{&offlinehttp.Request{}}, &options) + template.Executer = tmplexec.NewTemplateExecuter([]protocols.Request{&offlinehttp.Request{}}, &options) return nil } @@ -200,20 +235,87 @@ mainLoop: // ParseTemplateFromReader reads the template from reader // returns the parsed template func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, options protocols.ExecutorOptions) (*Template, error) { - template := &Template{} data, err := io.ReadAll(reader) if err != nil { return nil, err } - data = template.expandPreprocessors(data) - if preprocessor != nil { - data = preprocessor.Process(data) + // a preprocessor is a variable like + // {{randstr}} which is replaced before unmarshalling + // as it is known to be a random static value per template + hasPreprocessor := false + allPreprocessors := getPreprocessors(preprocessor) + for _, preprocessor := range allPreprocessors { + if preprocessor.Exists(data) { + hasPreprocessor = true + break + } } - if err := yaml.Unmarshal(data, template); err != nil { + if !hasPreprocessor { + // if no preprocessors exists parse template and exit + template, err := parseTemplate(data, options) + if err != nil { + return nil, err + } + if !template.Verified && len(template.Workflows) == 0 { + if config.DefaultConfig.LogAllEvents { + gologger.DefaultLogger.Print().Msgf("[%v] Template %s is not signed or tampered\n", aurora.Yellow("WRN").String(), template.ID) + } + SignatureStats[Unsigned].Add(1) + } + return template, nil + } + + // if preprocessor is required / exists in this template + // first unmarshal it and check if its verified + // persist verified status value and then + // expand all preprocessor and reparse template + + // === signature verification befoer preprocessors === + template, err := parseTemplate(data, options) + if err != nil { return nil, err } + isVerified := template.Verified + if !template.Verified && len(template.Workflows) == 0 { + // workflows are not signed by default + if config.DefaultConfig.LogAllEvents { + gologger.DefaultLogger.Print().Msgf("[%v] Template %s is not signed or tampered\n", aurora.Yellow("WRN").String(), template.ID) + } + SignatureStats[Unsigned].Add(1) + } + + // ==== execute preprocessors ====== + for _, v := range allPreprocessors { + data = v.Process(data) + } + reParsed, err := parseTemplate(data, options) + if err != nil { + return nil, err + } + reParsed.Verified = isVerified + return reParsed, nil +} + +// this method does not include any kind of preprocessing +func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, error) { + template := &Template{} + var err error + switch config.GetTemplateFormatFromExt(template.Path) { + case config.JSON: + err = json.Unmarshal(data, template) + case config.YAML: + err = yaml.Unmarshal(data, template) + default: + // assume its yaml + if err = yaml.Unmarshal(data, template); err != nil { + return nil, err + } + } + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to parse %s", template.Path) + } if utils.IsBlank(template.Info.Name) { return nil, errors.New("no template name field provided") @@ -231,13 +333,33 @@ func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, option options.Variables = template.Variables } + // if more than 1 request per protocol exist we add request id to protocol request + // since in template context we have proto_prefix for each protocol it is overwritten + // if request id is not present + template.validateAllRequestIDs() + + // create empty context args for template scope + options.CreateTemplateCtxStore() + options.ProtocolType = template.Type() options.Constants = template.Constants + // initialize the js compiler if missing + if options.JsCompiler == nil { + options.JsCompiler = GetJsCompiler() + } + + template.Options = &options // If no requests, and it is also not a workflow, return error. if template.Requests() == 0 { return nil, fmt.Errorf("no requests defined for %s", template.ID) } + // load `flow` and `source` in code protocol from file + // if file is referenced instead of actual source code + if err := template.ImportFileRefs(template.Options); err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to load file refs for %s", template.ID) + } + if err := template.compileProtocolRequests(options); err != nil { return nil, err } @@ -253,5 +375,26 @@ func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, option } template.parseSelfContainedRequests() + // check if the template is verified + // only valid templates can be verified or signed + for _, verifier := range signer.DefaultTemplateVerifiers { + template.Verified, _ = verifier.Verify(data, template) + if template.Verified { + SignatureStats[verifier.Identifier()].Add(1) + break + } + } return template, nil } + +var ( + jsCompiler *compiler.Compiler + jsCompilerOnce = sync.OnceFunc(func() { + jsCompiler = compiler.New() + }) +) + +func GetJsCompiler() *compiler.Compiler { + jsCompilerOnce() + return jsCompiler +} diff --git a/v2/pkg/templates/compile_test.go b/v2/pkg/templates/compile_test.go index 037b2670..ec2055ac 100644 --- a/v2/pkg/templates/compile_test.go +++ b/v2/pkg/templates/compile_test.go @@ -36,7 +36,7 @@ var executerOpts protocols.ExecutorOptions func setup() { options := testutils.DefaultOptions testutils.Init(options) - progressImpl, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) executerOpts = protocols.ExecutorOptions{ Output: testutils.NewMockOutputWriter(), @@ -53,7 +53,6 @@ func setup() { log.Fatalf("Could not create workflow loader: %s\n", err) } executerOpts.WorkflowLoader = workflowLoader - } func Test_ParseFromURL(t *testing.T) { diff --git a/v2/pkg/templates/log.go b/v2/pkg/templates/log.go index 7d825915..d705a86e 100644 --- a/v2/pkg/templates/log.go +++ b/v2/pkg/templates/log.go @@ -6,6 +6,7 @@ import ( "github.com/logrusorgru/aurora" "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" mapsutil "github.com/projectdiscovery/utils/maps" ) @@ -57,7 +58,7 @@ func PrintDeprecatedProtocolNameMsgIfApplicable(isSilent bool, verbose bool) { if count > 0 && !isSilent { gologger.Print().Msgf("[%v] Found %v templates loaded with deprecated protocol syntax, update before v3 for continued support.\n", aurora.Yellow("WRN").String(), count) } - if verbose { + if config.DefaultConfig.LogAllEvents { _ = deprecatedProtocolNameTemplates.Iterate(func(k string, v bool) error { gologger.Print().Msgf(" - %s\n", k) return nil diff --git a/v2/pkg/templates/preprocessors.go b/v2/pkg/templates/preprocessors.go index cc76ddc7..f730ec09 100644 --- a/v2/pkg/templates/preprocessors.go +++ b/v2/pkg/templates/preprocessors.go @@ -9,13 +9,36 @@ import ( ) type Preprocessor interface { + // Process processes the data and returns the processed data. Process(data []byte) []byte + // Exists check if the preprocessor exists in the data. + Exists(data []byte) bool } -var preprocessorRegex = regexp.MustCompile(`{{([a-z0-9_]+)}}`) +var ( + preprocessorRegex = regexp.MustCompile(`{{([a-z0-9_]+)}}`) + defaultPreprocessors = []Preprocessor{} +) -// expandPreprocessors expands the pre-processors if any for a template data. -func (template *Template) expandPreprocessors(data []byte) []byte { +func getPreprocessors(preprocessor Preprocessor) []Preprocessor { + if preprocessor != nil { + // append() function adds the elements to existing slice if space is available + // else it creates a new slice and copies the elements to new slice + // this may cause race-conditions hence we do it explicitly + tmp := make([]Preprocessor, 0, len(defaultPreprocessors)+1) + tmp = append(tmp, preprocessor) + tmp = append(tmp, defaultPreprocessors...) + return tmp + } + return defaultPreprocessors +} + +var _ Preprocessor = &randStrPreprocessor{} + +type randStrPreprocessor struct{} + +// Process processes the data and returns the processed data. +func (r *randStrPreprocessor) Process(data []byte) []byte { foundMap := make(map[string]struct{}) for _, expression := range preprocessorRegex.FindAllStringSubmatch(string(data), -1) { @@ -37,3 +60,8 @@ func (template *Template) expandPreprocessors(data []byte) []byte { } return data } + +// Exists check if the preprocessor exists in the data. +func (r *randStrPreprocessor) Exists(data []byte) bool { + return bytes.Contains(data, []byte("randstr")) +} diff --git a/v2/pkg/templates/signer/default.go b/v2/pkg/templates/signer/default.go index bf8b08e0..c05eda94 100644 --- a/v2/pkg/templates/signer/default.go +++ b/v2/pkg/templates/signer/default.go @@ -1,7 +1,41 @@ package signer -var DefaultVerifier *Signer +import ( + "github.com/projectdiscovery/gologger" + v2 "github.com/projectdiscovery/nuclei/v2" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + errorutil "github.com/projectdiscovery/utils/errors" +) + +// DefaultTemplateVerifiers contains the default template verifiers +var DefaultTemplateVerifiers []*TemplateSigner func init() { - DefaultVerifier, _ = NewVerifier(&Options{PublicKeyData: ecdsaPublicKey, Algorithm: ECDSA}) + h := &KeyHandler{ + UserCert: v2.NucleiCert, + } + if err := h.ParseUserCert(); err != nil { + gologger.Error().Msgf("Could not parse pd nuclei certificate: %s\n", err) + return + } + DefaultTemplateVerifiers = append(DefaultTemplateVerifiers, &TemplateSigner{handler: h}) + + // try to load default user cert + usr := &KeyHandler{} + if err := usr.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err == nil { + if err := usr.ParseUserCert(); err != nil { + gologger.Error().Msgf("malformed user cert found: %s\n", err) + return + } + DefaultTemplateVerifiers = append(DefaultTemplateVerifiers, &TemplateSigner{handler: usr}) + } +} + +// AddSignerToDefault adds a signer to the default list of signers +func AddSignerToDefault(s *TemplateSigner) error { + if s == nil { + return errorutil.New("signer is nil") + } + DefaultTemplateVerifiers = append(DefaultTemplateVerifiers, s) + return nil } diff --git a/v2/pkg/templates/signer/ecdsa_public_key.go b/v2/pkg/templates/signer/ecdsa_public_key.go deleted file mode 100644 index ea32cbfc..00000000 --- a/v2/pkg/templates/signer/ecdsa_public_key.go +++ /dev/null @@ -1,8 +0,0 @@ -package signer - -import ( - _ "embed" -) - -//go:embed ecdsa_public_key.pem -var ecdsaPublicKey []byte diff --git a/v2/pkg/templates/signer/ecdsa_public_key.pem b/v2/pkg/templates/signer/ecdsa_public_key.pem deleted file mode 100644 index 3fe5969e..00000000 --- a/v2/pkg/templates/signer/ecdsa_public_key.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ------END PUBLIC KEY----- diff --git a/v2/pkg/templates/signer/handler.go b/v2/pkg/templates/signer/handler.go new file mode 100644 index 00000000..86071914 --- /dev/null +++ b/v2/pkg/templates/signer/handler.go @@ -0,0 +1,292 @@ +package signer + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/projectdiscovery/gologger" + fileutil "github.com/projectdiscovery/utils/file" + "github.com/rs/xid" + "golang.org/x/term" +) + +const ( + CertType = "PD NUCLEI USER CERTIFICATE" + PrivateKeyType = "PD NUCLEI USER PRIVATE KEY" + CertFilename = "nuclei-user.crt" + PrivateKeyFilename = "nuclei-user-private-key.pem" + CertEnvVarName = "NUCLEI_USER_CERTIFICATE" + PrivateKeyEnvName = "NUCLEI_USER_PRIVATE_KEY" +) + +var ( + ErrNoCertificate = fmt.Errorf("nuclei user certificate not found") + ErrNoPrivateKey = fmt.Errorf("nuclei user private key not found") + SkipGeneratingKeys = false + noUserPassphrase = false +) + +// KeyHandler handles the key generation and management +// of signer public and private keys +type KeyHandler struct { + UserCert []byte + PrivateKey []byte + cert *x509.Certificate + ecdsaPubKey *ecdsa.PublicKey + ecdsaKey *ecdsa.PrivateKey +} + +// ReadUserCert reads the user certificate from environment variable or given directory +func (k *KeyHandler) ReadCert(envName, dir string) error { + // read from env + if cert := k.getEnvContent(envName); cert != nil { + k.UserCert = cert + return nil + } + // read from disk + if cert, err := os.ReadFile(filepath.Join(dir, CertFilename)); err == nil { + k.UserCert = cert + return nil + } + return ErrNoCertificate +} + +// ReadPrivateKey reads the private key from environment variable or given directory +func (k *KeyHandler) ReadPrivateKey(envName, dir string) error { + // read from env + if privateKey := k.getEnvContent(envName); privateKey != nil { + k.PrivateKey = privateKey + return nil + } + // read from disk + if privateKey, err := os.ReadFile(filepath.Join(dir, PrivateKeyFilename)); err == nil { + k.PrivateKey = privateKey + return nil + } + return ErrNoPrivateKey +} + +// ParseUserCert parses the user certificate and returns the public key +func (k *KeyHandler) ParseUserCert() error { + block, _ := pem.Decode(k.UserCert) + if block == nil { + return fmt.Errorf("failed to parse PEM block containing the certificate") + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return err + } + if cert.Subject.CommonName == "" { + return fmt.Errorf("invalid certificate: expected common name to be set") + } + k.cert = cert + var ok bool + k.ecdsaPubKey, ok = cert.PublicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("failed to parse ecdsa public key from cert") + } + return nil +} + +// ParsePrivateKey parses the private key and returns the private key +func (k *KeyHandler) ParsePrivateKey() error { + block, _ := pem.Decode(k.PrivateKey) + if block == nil { + return fmt.Errorf("failed to parse PEM block containing the private key") + } + // if pem block is encrypted , decrypt it + if x509.IsEncryptedPEMBlock(block) { // nolint: all + gologger.Info().Msgf("Private Key is encrypted with passphrase") + fmt.Printf("[*] Enter passphrase (exit to abort): ") + bin, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return err + } + fmt.Println() + if string(bin) == "exit" { + return fmt.Errorf("private key requires passphrase, but none was provided") + } + block.Bytes, err = x509.DecryptPEMBlock(block, bin) // nolint: all + if err != nil { + return err + } + } + var err error + k.ecdsaKey, err = x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return err + } + return nil +} + +// GenerateKeyPair generates a new key-pair for signing code templates +func (k *KeyHandler) GenerateKeyPair() { + + gologger.Info().Msgf("Generating new key-pair for signing templates") + fmt.Printf("[*] Enter User/Organization Name (exit to abort) : ") + + // get user/organization name + identifier := "" + _, err := fmt.Scanln(&identifier) + if err != nil { + gologger.Fatal().Msgf("failed to read user/organization name: %s", err) + } + if identifier == "exit" { + gologger.Fatal().Msgf("exiting key-pair generation") + } + if identifier == "" { + gologger.Fatal().Msgf("user/organization name cannot be empty") + } + + // generate new key-pair + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + gologger.Fatal().Msgf("failed to generate ecdsa key-pair: %s", err) + } + + // create x509 certificate with user/organization name and public key + // self-signed certificate with generated private key + k.UserCert, err = k.generateCertWithKey(identifier, privateKey) + if err != nil { + gologger.Fatal().Msgf("failed to create certificate: %s", err) + } + + // marshal private key + k.PrivateKey, err = k.marshalPrivateKey(privateKey) + if err != nil { + gologger.Fatal().Msgf("failed to marshal ecdsa private key: %s", err) + } + gologger.Info().Msgf("Successfully generated new key-pair for signing templates") +} + +// SaveToDisk saves the generated key-pair to the given directory +func (k *KeyHandler) SaveToDisk(dir string) error { + _ = fileutil.FixMissingDirs(filepath.Join(dir, CertFilename)) // not required but just in case will take care of missing dirs in path + if err := os.WriteFile(filepath.Join(dir, CertFilename), k.UserCert, 0600); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(dir, PrivateKeyFilename), k.PrivateKey, 0600); err != nil { + return err + } + return nil +} + +// getEnvContent returns the content of the environment variable +// if it is a file then it loads its content +func (k *KeyHandler) getEnvContent(name string) []byte { + val := os.Getenv(name) + if val == "" { + return nil + } + if fileutil.FileExists(val) { + data, err := os.ReadFile(val) + if err != nil { + gologger.Fatal().Msgf("failed to read file: %s", err) + } + return data + } + return []byte(val) +} + +// generateCertWithKey creates a self-signed certificate with the given identifier and private key +func (k *KeyHandler) generateCertWithKey(identifier string, privateKey *ecdsa.PrivateKey) ([]byte, error) { + // Setting up the certificate + notBefore := time.Now() + notAfter := notBefore.Add(4 * 365 * 24 * time.Hour) + + serialNumber := big.NewInt(xid.New().Time().Unix()) + // create certificate template + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: identifier, + }, + SignatureAlgorithm: x509.ECDSAWithSHA256, + NotBefore: notBefore, + NotAfter: notAfter, + PublicKey: &privateKey.PublicKey, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + }, + IsCA: false, + BasicConstraintsValid: true, + } + // Create the certificate + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, err + } + + var certOut bytes.Buffer + if err := pem.Encode(&certOut, &pem.Block{Type: CertType, Bytes: derBytes}); err != nil { + return nil, err + } + return certOut.Bytes(), nil +} + +// marshalPrivateKey marshals the private key and encrypts it with the given passphrase +func (k *KeyHandler) marshalPrivateKey(privateKey *ecdsa.PrivateKey) ([]byte, error) { + + var passphrase []byte + // get passphrase to encrypt private key before saving to disk + if !noUserPassphrase { + fmt.Printf("[*] Enter passphrase (exit to abort): ") + passphrase = getPassphrase() + } + + // marshal private key + privateKeyData, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + gologger.Fatal().Msgf("failed to marshal ecdsa private key: %s", err) + } + // pem encode keys + pemBlock := &pem.Block{ + Type: PrivateKeyType, Bytes: privateKeyData, + } + // encrypt private key if passphrase is provided + if len(passphrase) > 0 { + // encode it with passphrase + // this function is deprecated since go 1.16 but go stdlib does not want to provide any alternative + // see: https://github.com/golang/go/issues/8860 + encBlock, err := x509.EncryptPEMBlock(rand.Reader, pemBlock.Type, pemBlock.Bytes, passphrase, x509.PEMCipherAES256) // nolint: all + if err != nil { + gologger.Fatal().Msgf("failed to encrypt private key: %s", err) + } + pemBlock = encBlock + } + return pem.EncodeToMemory(pemBlock), nil +} + +func getPassphrase() []byte { + bin, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + gologger.Fatal().Msgf("could not read passphrase: %s", err) + } + fmt.Println() + if string(bin) == "exit" { + gologger.Fatal().Msgf("exiting") + } + fmt.Printf("[*] Enter same passphrase again: ") + bin2, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + gologger.Fatal().Msgf("could not read passphrase: %s", err) + } + fmt.Println() + // review: should we allow empty passphrase? + // we currently allow empty passphrase + if string(bin) != string(bin2) { + gologger.Fatal().Msgf("passphrase did not match try again") + } + return bin +} diff --git a/v2/pkg/templates/signer/handler_test.go b/v2/pkg/templates/signer/handler_test.go new file mode 100644 index 00000000..449e2aee --- /dev/null +++ b/v2/pkg/templates/signer/handler_test.go @@ -0,0 +1,60 @@ +package signer + +import ( + "bytes" + "os" + "os/exec" + "strings" + "testing" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" +) + +// This Unit Test generates a new key pair and parses it +// to ensure that the key handler works as expected. +func TestKeyHandler(t *testing.T) { + if val := os.Getenv("KEY_HANDLER_CI"); val != "1" { + cmd := exec.Command(os.Args[0], "-test.run=^TestKeyHandler$", "-test.v") + cmd.Env = append(cmd.Env, "KEY_HANDLER_CI=1") + var buff bytes.Buffer + cmd.Stdin = &buff + buff.WriteString("CIUSER\n") + buff.WriteString("\n") + out, err := cmd.CombinedOutput() + if !strings.Contains(string(out), "PASS\n") || err != nil { + t.Fatalf("%s\n(exit status %v)", string(out), err) + } + return + } + gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + h := &KeyHandler{} + noUserPassphrase = true + h.GenerateKeyPair() + if h.UserCert == nil { + t.Fatal("no user cert found") + } + if h.PrivateKey == nil { + t.Fatal("no private key found") + } + + // now parse the cert and private key + if err := h.ParseUserCert(); err != nil { + t.Fatal(err) + } + if err := h.ParsePrivateKey(); err != nil { + t.Fatal(err) + } + if h.ecdsaKey == nil { + t.Fatal("no ecdsa key found") + } + if h.ecdsaPubKey == nil { + t.Fatal("no ecdsa public key found") + } + if h.cert == nil { + t.Fatal("no certificate found") + } + if h.cert.Subject.CommonName != "CIUSER" { + t.Fatal("invalid user name found") + } +} diff --git a/v2/pkg/templates/signer/options.go b/v2/pkg/templates/signer/options.go deleted file mode 100644 index 8cb94bb6..00000000 --- a/v2/pkg/templates/signer/options.go +++ /dev/null @@ -1,34 +0,0 @@ -package signer - -import ( - "errors" - "math/big" - "regexp" -) - -type AlgorithmType uint8 - -const ( - RSA AlgorithmType = iota - ECDSA -) - -type Options struct { - PrivateKeyName string - PrivateKeyData []byte - PassphraseName string - PassphraseData []byte - PublicKeyName string - PublicKeyData []byte - Algorithm AlgorithmType -} - -type EcdsaSignature struct { - R *big.Int - S *big.Int -} - -var ( - ReDigest = regexp.MustCompile(`(?m)^#\sdigest:\s.+$`) - ErrUnknownAlgorithm = errors.New("unknown algorithm") -) diff --git a/v2/pkg/templates/signer/signer.go b/v2/pkg/templates/signer/signer.go deleted file mode 100644 index 87e3cff7..00000000 --- a/v2/pkg/templates/signer/signer.go +++ /dev/null @@ -1,234 +0,0 @@ -package signer - -import ( - "bytes" - "crypto/ecdsa" - "crypto/rand" - "crypto/sha256" - "crypto/x509" - "encoding/gob" - "encoding/pem" - "errors" - "fmt" - "os" - - fileutil "github.com/projectdiscovery/utils/file" - "golang.org/x/crypto/ssh" -) - -type Signer struct { - options *Options - sshSigner ssh.Signer - sshVerifier ssh.PublicKey - ecdsaSigner *ecdsa.PrivateKey - ecdsaVerifier *ecdsa.PublicKey -} - -func New(options *Options) (*Signer, error) { - var ( - privateKeyData, passphraseData, publicKeyData []byte - err error - ) - if options.PrivateKeyName != "" { - privateKeyData, err = readKeyFromFileOrEnv(options.PrivateKeyName) - if err != nil { - return nil, err - } - } else { - privateKeyData = options.PrivateKeyData - } - - if options.PassphraseName != "" { - passphraseData = readKeyFromFileOrEnvWithDefault(options.PassphraseName, []byte{}) - } else { - passphraseData = options.PassphraseData - } - - if options.PublicKeyName != "" { - publicKeyData, err = readKeyFromFileOrEnv(options.PublicKeyName) - if err != nil { - return nil, err - } - } else { - publicKeyData = options.PublicKeyData - } - - signer := &Signer{options: options} - - switch signer.options.Algorithm { - case RSA: - signer.sshSigner, signer.sshVerifier, err = parseRsa(privateKeyData, publicKeyData, passphraseData) - case ECDSA: - signer.ecdsaSigner, signer.ecdsaVerifier, err = parseECDSA(privateKeyData, publicKeyData) - default: - return nil, ErrUnknownAlgorithm - } - - if err != nil { - return nil, err - } - - return signer, nil -} - -func NewVerifier(options *Options) (*Signer, error) { - var ( - publicKeyData []byte - err error - ) - if options.PublicKeyName != "" { - publicKeyData, err = readKeyFromFileOrEnv(options.PrivateKeyName) - if err != nil { - return nil, err - } - } else { - publicKeyData = options.PublicKeyData - } - - signer := &Signer{options: options} - - switch signer.options.Algorithm { - case RSA: - signer.sshVerifier, err = parseRsaPublicKey(publicKeyData) - case ECDSA: - signer.ecdsaVerifier, err = parseECDSAPublicKey(publicKeyData) - default: - return nil, ErrUnknownAlgorithm - } - - if err != nil { - return nil, err - } - - return signer, nil -} - -func (s *Signer) Sign(data []byte) ([]byte, error) { - dataHash := sha256.Sum256(data) - switch s.options.Algorithm { - case RSA: - sshSignature, err := s.sshSigner.Sign(rand.Reader, dataHash[:]) - if err != nil { - return nil, err - } - var signatureData bytes.Buffer - if err := gob.NewEncoder(&signatureData).Encode(sshSignature); err != nil { - return nil, err - } - return signatureData.Bytes(), nil - case ECDSA: - r, s, err := ecdsa.Sign(rand.Reader, s.ecdsaSigner, dataHash[:]) - if err != nil { - return nil, err - } - ecdsaSignature := &EcdsaSignature{R: r, S: s} - var signatureData bytes.Buffer - if err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil { - return nil, err - } - return signatureData.Bytes(), nil - default: - return nil, ErrUnknownAlgorithm - } -} - -func (s *Signer) Verify(data, signatureData []byte) (bool, error) { - dataHash := sha256.Sum256(data) - switch s.options.Algorithm { - case RSA: - signature := &ssh.Signature{} - if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil { - return false, err - } - if err := s.sshVerifier.Verify(dataHash[:], signature); err != nil { - return false, err - } - return true, nil - case ECDSA: - signature := &EcdsaSignature{} - if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil { - return false, err - } - return ecdsa.Verify(s.ecdsaVerifier, dataHash[:], signature.R, signature.S), nil - default: - return false, ErrUnknownAlgorithm - } -} - -func parseRsa(privateKeyData, passphraseData, publicKeyData []byte) (ssh.Signer, ssh.PublicKey, error) { - privateKey, err := parseRsaPrivateKey(privateKeyData, passphraseData) - if err != nil { - return nil, nil, err - } - - publicKey, err := parseRsaPublicKey(publicKeyData) - if err != nil { - return nil, nil, err - } - - return privateKey, publicKey, nil -} - -func parseRsaPrivateKey(privateKeyData, passphraseData []byte) (ssh.Signer, error) { - if len(passphraseData) > 0 { - return ssh.ParsePrivateKeyWithPassphrase(privateKeyData, passphraseData) - } - return ssh.ParsePrivateKey(privateKeyData) -} - -func parseRsaPublicKey(publicKeyData []byte) (ssh.PublicKey, error) { - publicKey, _, _, _, err := ssh.ParseAuthorizedKey(publicKeyData) - if err != nil { - return nil, err - } - - return publicKey, nil -} - -func parseECDSA(privateKeyData, publicKeyData []byte) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { - privateKey, err := parseECDSAPrivateKey(privateKeyData) - if err != nil { - return nil, nil, err - } - publicKey, err := parseECDSAPublicKey(publicKeyData) - if err != nil { - return nil, nil, err - } - return privateKey, publicKey, nil -} - -func parseECDSAPrivateKey(privateKeyData []byte) (*ecdsa.PrivateKey, error) { - blockPriv, _ := pem.Decode(privateKeyData) - return x509.ParseECPrivateKey(blockPriv.Bytes) -} - -func parseECDSAPublicKey(publicKeyData []byte) (*ecdsa.PublicKey, error) { - blockPub, _ := pem.Decode(publicKeyData) - genericPublicKey, err := x509.ParsePKIXPublicKey(blockPub.Bytes) - if err != nil { - return nil, err - } - if publicKey, ok := genericPublicKey.(*ecdsa.PublicKey); ok { - return publicKey, nil - } - - return nil, errors.New("couldn't parse ecdsa public key") -} - -func readKeyFromFileOrEnvWithDefault(keypath string, defaultValue []byte) []byte { - keyValue, err := readKeyFromFileOrEnv(keypath) - if err != nil { - return defaultValue - } - return keyValue -} - -func readKeyFromFileOrEnv(keypath string) ([]byte, error) { - if fileutil.FileExists(keypath) { - return os.ReadFile(keypath) - } - if keydata := os.Getenv(keypath); keydata != "" { - return []byte(keydata), nil - } - return nil, fmt.Errorf("Private key not found in file or environment variable: %s", keypath) -} diff --git a/v2/pkg/templates/signer/tmpl_signer.go b/v2/pkg/templates/signer/tmpl_signer.go new file mode 100644 index 00000000..0c1ae49e --- /dev/null +++ b/v2/pkg/templates/signer/tmpl_signer.go @@ -0,0 +1,234 @@ +package signer + +import ( + "bytes" + "crypto/ecdsa" + "crypto/md5" + "crypto/rand" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "errors" + "fmt" + "os" + "regexp" + "strings" + "sync" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + errorutil "github.com/projectdiscovery/utils/errors" +) + +var ( + ReDigest = regexp.MustCompile(`(?m)^#\sdigest:\s.+$`) + ErrUnknownAlgorithm = errors.New("unknown algorithm") + SignaturePattern = "# digest: " + SignatureFmt = SignaturePattern + "%x" + ":%v" // `#digest: :` +) + +func RemoveSignatureFromData(data []byte) []byte { + return bytes.Trim(ReDigest.ReplaceAll(data, []byte("")), "\n") +} + +func GetSignatureFromData(data []byte) []byte { + return ReDigest.Find(data) +} + +// SignableTemplate is a template that can be signed +type SignableTemplate interface { + // GetFileImports returns a list of files that are imported by the template + GetFileImports() []string + // HasCodeProtocol returns true if the template has a code protocol section + HasCodeProtocol() bool +} + +type TemplateSigner struct { + sync.Once + handler *KeyHandler + fragment string +} + +// Identifier returns the identifier for the template signer +func (t *TemplateSigner) Identifier() string { + return t.handler.cert.Subject.CommonName +} + +// fragment is optional part of signature that is used to identify the user +// who signed the template via md5 hash of public key +func (t *TemplateSigner) GetUserFragment() string { + // wrap with sync.Once to reduce unnecessary md5 hashing + t.Do(func() { + if t.handler.ecdsaPubKey != nil { + hashed := md5.Sum(t.handler.ecdsaPubKey.X.Bytes()) + t.fragment = fmt.Sprintf("%x", hashed) + } + }) + return t.fragment +} + +// Sign signs the given template with the template signer and returns the signature +func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) { + // while re-signing template check if it has a code protocol + // if it does then verify that it is signed by current signer + // if not then return error + if tmpl.HasCodeProtocol() { + sig := GetSignatureFromData(data) + arr := strings.SplitN(string(sig), ":", 3) + if len(arr) == 2 { + // signature has no fragment + return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.") + } + if len(arr) == 3 { + // signature has fragment verify if it is equal to current fragment + fragment := t.GetUserFragment() + if fragment != arr[2] { + return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.") + } + } + } + + buff := bytes.NewBuffer(RemoveSignatureFromData(data)) + // if file has any imports process them + for _, file := range tmpl.GetFileImports() { + bin, err := os.ReadFile(file) + if err != nil { + return "", err + } + buff.WriteRune('\n') + buff.Write(bin) + } + signatureData, err := t.sign(buff.Bytes()) + if err != nil { + return "", err + } + return signatureData, nil +} + +// Signs given data with the template signer +// Note: this should not be used for signing templates as file references +// in templates are not processed use template.SignTemplate() instead +func (t *TemplateSigner) sign(data []byte) (string, error) { + dataHash := sha256.Sum256(data) + ecdsaSignature, err := ecdsa.SignASN1(rand.Reader, t.handler.ecdsaKey, dataHash[:]) + if err != nil { + return "", err + } + var signatureData bytes.Buffer + if err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil { + return "", err + } + return fmt.Sprintf(SignatureFmt, signatureData.Bytes(), t.GetUserFragment()), nil +} + +// Verify verifies the given template with the template signer +func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) { + digestData := ReDigest.Find(data) + if len(digestData) == 0 { + return false, errors.New("digest not found") + } + + digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern))) + // remove fragment from digest as it is used for re-signing purposes only + digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment()) + digest, err := hex.DecodeString(digestString) + if err != nil { + return false, err + } + + buff := bytes.NewBuffer(RemoveSignatureFromData(data)) + // if file has any imports process them + for _, file := range tmpl.GetFileImports() { + bin, err := os.ReadFile(file) + if err != nil { + return false, err + } + buff.WriteRune('\n') + buff.Write(bin) + } + + return t.verify(buff.Bytes(), digest) +} + +// Verify verifies the given data with the template signer +// Note: this should not be used for verifying templates as file references +// in templates are not processed +func (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) { + dataHash := sha256.Sum256(data) + + var signature []byte + if err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil { + return false, err + } + return ecdsa.VerifyASN1(t.handler.ecdsaPubKey, dataHash[:], signature), nil +} + +// NewTemplateSigner creates a new signer for signing templates +func NewTemplateSigner(cert, privateKey []byte) (*TemplateSigner, error) { + handler := &KeyHandler{} + var err error + if cert != nil || privateKey != nil { + handler.UserCert = cert + handler.PrivateKey = privateKey + } else { + err = handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()) + if err == nil { + err = handler.ReadPrivateKey(PrivateKeyEnvName, config.DefaultConfig.GetKeysDir()) + } + } + if err != nil && !SkipGeneratingKeys { + if err != ErrNoCertificate && err != ErrNoPrivateKey { + gologger.Info().Msgf("Invalid user cert found : %s\n", err) + } + // generating new keys + handler.GenerateKeyPair() + if err := handler.SaveToDisk(config.DefaultConfig.GetKeysDir()); err != nil { + gologger.Fatal().Msgf("could not save generated keys to disk: %s\n", err) + } + // do not continue further let user re-run the command + os.Exit(0) + } else if err != nil && SkipGeneratingKeys { + return nil, err + } + + if err := handler.ParseUserCert(); err != nil { + return nil, err + } + if err := handler.ParsePrivateKey(); err != nil { + return nil, err + } + return &TemplateSigner{ + handler: handler, + }, nil +} + +// NewTemplateSignerFromFiles creates a new signer for signing templates +func NewTemplateSignerFromFiles(cert, privKey string) (*TemplateSigner, error) { + certData, err := os.ReadFile(cert) + if err != nil { + return nil, err + } + privKeyData, err := os.ReadFile(privKey) + if err != nil { + return nil, err + } + return NewTemplateSigner(certData, privKeyData) +} + +// NewTemplateSigVerifier creates a new signer for verifying templates +func NewTemplateSigVerifier(cert []byte) (*TemplateSigner, error) { + handler := &KeyHandler{} + if cert != nil { + handler.UserCert = cert + } else { + if err := handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err != nil { + return nil, err + } + } + if err := handler.ParseUserCert(); err != nil { + return nil, err + } + return &TemplateSigner{ + handler: handler, + }, nil +} diff --git a/v2/pkg/templates/signer/util.go b/v2/pkg/templates/signer/util.go deleted file mode 100644 index 70f4308c..00000000 --- a/v2/pkg/templates/signer/util.go +++ /dev/null @@ -1,50 +0,0 @@ -package signer - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" -) - -const ( - SignaturePattern = "# digest: " - SignatureFmt = SignaturePattern + "%x" -) - -func RemoveSignatureFromData(data []byte) []byte { - return bytes.Trim(ReDigest.ReplaceAll(data, []byte("")), "\n") -} - -func Sign(sign *Signer, data []byte) (string, error) { - if sign == nil { - return "", errors.New("invalid nil signer") - } - cleanedData := RemoveSignatureFromData(data) - signatureData, err := sign.Sign(cleanedData) - if err != nil { - return "", err - } - - return fmt.Sprintf(SignatureFmt, signatureData), nil -} - -func Verify(sign *Signer, data []byte) (bool, error) { - if sign == nil { - return false, errors.New("invalid nil verifier") - } - digestData := ReDigest.Find(data) - if len(digestData) == 0 { - return false, errors.New("digest not found") - } - - digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern))) - digest, err := hex.DecodeString(string(digestData)) - if err != nil { - return false, err - } - - cleanedData := RemoveSignatureFromData(data) - - return sign.Verify(cleanedData, digest) -} diff --git a/v2/pkg/templates/template_sign.go b/v2/pkg/templates/template_sign.go new file mode 100644 index 00000000..58e60fdb --- /dev/null +++ b/v2/pkg/templates/template_sign.go @@ -0,0 +1,99 @@ +package templates + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/extensions" + "github.com/projectdiscovery/nuclei/v2/pkg/templates/signer" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + errorutil "github.com/projectdiscovery/utils/errors" +) + +// Due to file references in sensitive fields of template +// ex: javascript code in flow or bash command in code.Source etc +// signing / verifying template is only possible after loading the template +// with these fields resolved + +var ( + defaultOpts *types.Options = types.DefaultOptions() + initOnce = sync.OnceFunc(func() { + _ = protocolstate.Init(defaultOpts) + _ = protocolinit.Init(defaultOpts) + }) + ErrNotATemplate = errorutil.NewWithTag("signer", "given filePath is not a template") +) + +// New Signer/Verification logic requires it to load content of file references +// and this is done respecting sandbox restrictions to avoid any security issues +// AllowLocalFileAccess is a function that allows local file access by disabling sandbox restrictions +// and **MUST** be called before signing / verifying any templates for intialization +func TemplateSignerLFA() { + defaultOpts.AllowLocalFileAccess = true +} + +// VerifyTemplateSignature verifies the signature of the template +// using default signers +func VerifyTemplateSignature(templatePath string) (bool, error) { + template, _, err := getTemplate(templatePath) + if err != nil { + return false, err + } + return template.Verified, nil +} + +// SignTemplate signs the tempalate using custom signer +func SignTemplate(templateSigner *signer.TemplateSigner, templatePath string) error { + // sign templates requires code files such as javsacript bash command to be included + // in template hence we first load template and append such resolved file references to content + initOnce() + + // signing is only supported on yaml nuclei templates + if !strings.HasSuffix(templatePath, extensions.YAML) { + return ErrNotATemplate + } + + template, bin, err := getTemplate(templatePath) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to get template from disk") + } + if len(template.Workflows) > 0 { + // signing workflows is not supported at least yet + return ErrNotATemplate + } + if !template.Verified { + signatureData, err := templateSigner.Sign(bin, template) + if err != nil { + return err + } + buff := bytes.NewBuffer(signer.RemoveSignatureFromData(bin)) + buff.WriteString("\n" + signatureData) + return os.WriteFile(templatePath, buff.Bytes(), 0644) + } + return nil +} + +func getTemplate(templatePath string) (*Template, []byte, error) { + catalog := disk.NewCatalog(filepath.Dir(templatePath)) + executerOpts := protocols.ExecutorOptions{ + Catalog: catalog, + Options: defaultOpts, + TemplatePath: templatePath, + } + bin, err := os.ReadFile(templatePath) + if err != nil { + return nil, bin, err + } + template, err := ParseTemplateFromReader(bytes.NewReader(bin), nil, executerOpts) + if err != nil { + return nil, bin, errorutil.NewWithErr(err).Msgf("failed to parse template") + } + return template, bin, nil +} diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go index e8d34208..b1b11967 100644 --- a/v2/pkg/templates/templates.go +++ b/v2/pkg/templates/templates.go @@ -3,15 +3,21 @@ package templates import ( "encoding/json" + "io" + "path/filepath" + "strconv" + "strings" validate "github.com/go-playground/validator/v10" "github.com/projectdiscovery/nuclei/v2/pkg/model" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/code" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/variables" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/file" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/javascript" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/network" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/ssl" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/websocket" @@ -19,6 +25,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/workflows" errorutil "github.com/projectdiscovery/utils/errors" + fileutil "github.com/projectdiscovery/utils/file" "go.uber.org/multierr" "gopkg.in/yaml.v2" ) @@ -45,6 +52,18 @@ type Template struct { // - value: exampleInfoStructure Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template"` // description: | + // Flow contains the execution flow for the template. + // examples: + // - flow: | + // for region in regions { + // http(0) + // } + // for vpc in vpcs { + // http(1) + // } + // + Flow string `yaml:"flow,omitempty" json:"flow,omitempty" jsonschema:"title=template execution flow in js,description=Flow contains js code which defines how the template should be executed"` + // description: | // Requests contains the http request to make in the template. // WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead. // examples: @@ -55,6 +74,7 @@ type Template struct { // examples: // - value: exampleNormalHTTPRequest // RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP + // Deprecated: Use RequestsHTTP instead. RequestsWithHTTP []*http.Request `yaml:"http,omitempty" json:"http,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"` // description: | // DNS contains the dns request to make in the template @@ -77,6 +97,7 @@ type Template struct { // examples: // - value: exampleNormalNetworkRequest // RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork + // Deprecated: Use RequestsNetwork instead. RequestsWithTCP []*network.Request `yaml:"tcp,omitempty" json:"tcp,omitempty" jsonschema:"title=network(tcp) requests to make,description=Network requests to make for the template"` // description: | // Headless contains the headless request to make in the template. @@ -87,10 +108,16 @@ type Template struct { // description: | // Websocket contains the Websocket request to make in the template. RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"` - // description: | // WHOIS contains the WHOIS request to make in the template. RequestsWHOIS []*whois.Request `yaml:"whois,omitempty" json:"whois,omitempty" jsonschema:"title=whois requests to make,description=WHOIS requests to make for the template"` + // description: | + // Code contains code snippets. + RequestsCode []*code.Request `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code snippets to make,description=Code snippets"` + // description: | + // Javascript contains the javascript request to make in the template. + RequestsJavascript []*javascript.Request `yaml:"javascript,omitempty" json:"javascript,omitempty" jsonschema:"title=javascript requests to make,description=Javascript requests to make for the template"` + // description: | // Workflows is a yaml based workflow declaration code. workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"` @@ -126,19 +153,12 @@ type Template struct { // Verified defines if the template signature is digitally verified Verified bool `yaml:"-" json:"-"` -} -// TemplateProtocols is a list of accepted template protocols -var TemplateProtocols = []string{ - "dns", - "file", - "http", - "headless", - "network", - "workflow", - "ssl", - "websocket", - "whois", + // RequestsQueue contains all template requests in order (both protocol & request order) + RequestsQueue []protocols.Request `yaml:"-" json:"-"` + + // ImportedFiles contains list of files whose contents are imported after template was compiled + ImportedFiles []string `yaml:"-" json:"-"` } // Type returns the type of the template @@ -162,14 +182,106 @@ func (template *Template) Type() types.ProtocolType { return types.WebsocketProtocol case len(template.RequestsWHOIS) > 0: return types.WHOISProtocol + case len(template.RequestsCode) > 0: + return types.CodeProtocol + case len(template.RequestsJavascript) > 0: + return types.JavascriptProtocol default: return types.InvalidProtocol } } +// HasCodeProtocol returns true if the template has a code protocol section +func (template *Template) HasCodeProtocol() bool { + return len(template.RequestsCode) > 0 +} + +// validateAllRequestIDs check if that protocol already has given id if not +// then is is manually set to proto_index +func (template *Template) validateAllRequestIDs() { + // this is required in multiprotocol and flow where we save response variables + // and all other data in template context if template as two requests in a protocol + // then it is overwritten to avoid this we use proto_index as request ID + if len(template.RequestsCode) > 1 { + for i, req := range template.RequestsCode { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + + if len(template.RequestsDNS) > 1 { + for i, req := range template.RequestsDNS { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsFile) > 1 { + for i, req := range template.RequestsFile { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsHTTP) > 1 { + for i, req := range template.RequestsHTTP { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsHeadless) > 1 { + for i, req := range template.RequestsHeadless { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + + } + if len(template.RequestsNetwork) > 1 { + for i, req := range template.RequestsNetwork { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsSSL) > 1 { + for i, req := range template.RequestsSSL { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsWebsocket) > 1 { + for i, req := range template.RequestsWebsocket { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsWHOIS) > 1 { + for i, req := range template.RequestsWHOIS { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } + if len(template.RequestsJavascript) > 1 { + for i, req := range template.RequestsJavascript { + if req.ID == "" { + req.ID = req.Type().String() + "_" + strconv.Itoa(i+1) + } + } + } +} + // MarshalYAML forces recursive struct validation during marshal operation func (template *Template) MarshalYAML() ([]byte, error) { out, marshalErr := yaml.Marshal(template) + // Review: we are adding requestIDs for templateContext + // if we are using this method then we might need to purge manually added IDS that start with `templatetype_` + // this is only applicable if there are more than 1 request fields in protocol errValidate := validate.New().Struct(template) return out, multierr.Append(marshalErr, errValidate) } @@ -200,7 +312,144 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error if len(alias.RequestsWithTCP) > 0 { template.RequestsNetwork = alias.RequestsWithTCP } - return validate.New().Struct(template) + err = validate.New().Struct(template) + if err != nil { + return err + } + // check if the template contains more than 1 protocol request + // if so preserve the order of the protocols and requests + if template.hasMultipleRequests() { + var tempmap yaml.MapSlice + err = unmarshal(&tempmap) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to unmarshal multi protocol template %s", template.ID) + } + arr := []string{} + for _, v := range tempmap { + key, ok := v.Key.(string) + if !ok { + continue + } + arr = append(arr, key) + } + // add protocols to the protocol stack (the idea is to preserve the order of the protocols) + template.addRequestsToQueue(arr...) + } + return nil +} + +// ImportFileRefs checks if sensitive fields like `flow` , `source` in code protocol are referencing files +// instead of actual javascript / engine code if so it loads the file contents and replaces the reference +func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error { + var errs []error + + loadFile := func(source string) (string, bool) { + // load file respecting sandbox + data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog) + if err == nil { + defer data.Close() + bin, err := io.ReadAll(data) + if err == nil { + return string(bin), true + } else { + errs = append(errs, err) + } + } else { + errs = append(errs, err) + } + return "", false + } + + // for code protocol requests + for _, request := range template.RequestsCode { + // simple test to check if source is a file or a snippet + if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) { + if val, ok := loadFile(request.Source); ok { + template.ImportedFiles = append(template.ImportedFiles, request.Source) + request.Source = val + } + } + } + + // flow code references + if template.Flow != "" { + if len(template.Flow) > 0 && filepath.Ext(template.Flow) == ".js" && fileutil.FileExists(template.Flow) { + if val, ok := loadFile(template.Flow); ok { + template.ImportedFiles = append(template.ImportedFiles, template.Flow) + template.Flow = val + } + } + options.Flow = template.Flow + } + + // for multiprotocol requests + // mutually exclusive with flow + if len(template.RequestsQueue) > 0 && template.Flow == "" { + // this is most likely a multiprotocol template + for _, req := range template.RequestsQueue { + if req.Type() == types.CodeProtocol { + request := req.(*code.Request) + // simple test to check if source is a file or a snippet + if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) { + if val, ok := loadFile(request.Source); ok { + template.ImportedFiles = append(template.ImportedFiles, request.Source) + request.Source = val + } + } + } + } + } + + return multierr.Combine(errs...) +} + +// GetFileImports returns a list of files that are imported by the template +func (template *Template) GetFileImports() []string { + return template.ImportedFiles +} + +// addProtocolsToQueue adds protocol requests to the queue and preserves order of the protocols and requests +func (template *Template) addRequestsToQueue(keys ...string) { + for _, key := range keys { + switch key { + case types.DNSProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsDNS)...) + case types.FileProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsFile)...) + case types.HTTPProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...) + case types.HeadlessProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...) + case types.NetworkProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...) + case types.SSLProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsSSL)...) + case types.WebsocketProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...) + case types.WHOISProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...) + case types.CodeProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsCode)...) + case types.JavascriptProtocol.String(): + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...) + // for deprecated protocols + case "requests": + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...) + case "network": + template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...) + } + } +} + +// hasMultipleRequests checks if the template has multiple requests +// if so it preserves the order of the request during compile and execution +func (template *Template) hasMultipleRequests() bool { + counter := len(template.RequestsDNS) + len(template.RequestsFile) + + len(template.RequestsHTTP) + len(template.RequestsHeadless) + + len(template.RequestsNetwork) + len(template.RequestsSSL) + + len(template.RequestsWebsocket) + len(template.RequestsWHOIS) + + len(template.RequestsCode) + len(template.RequestsJavascript) + return counter > 1 } // MarshalJSON forces recursive struct validation during marshal operation @@ -219,5 +468,23 @@ func (template *Template) UnmarshalJSON(data []byte) error { return err } *template = Template(*alias) - return validate.New().Struct(template) + err = validate.New().Struct(template) + if err != nil { + return err + } + // check if the template contains more than 1 protocol request + // if so preserve the order of the protocols and requests + if template.hasMultipleRequests() { + var tempMap map[string]interface{} + err = json.Unmarshal(data, &tempMap) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to unmarshal multi protocol template %s", template.ID) + } + arr := []string{} + for k := range tempMap { + arr = append(arr, k) + } + template.addRequestsToQueue(arr...) + } + return nil } diff --git a/v2/pkg/templates/templates_doc.go b/v2/pkg/templates/templates_doc.go index 37859049..527aeb3c 100644 --- a/v2/pkg/templates/templates_doc.go +++ b/v2/pkg/templates/templates_doc.go @@ -1238,7 +1238,7 @@ func init() { Value: "Headless response received from client (default)", }, } - HEADLESSRequestDoc.Fields = make([]encoder.Doc, 9) + HEADLESSRequestDoc.Fields = make([]encoder.Doc, 7) HEADLESSRequestDoc.Fields[0].Name = "id" HEADLESSRequestDoc.Fields[0].Type = "string" HEADLESSRequestDoc.Fields[0].Note = "" @@ -1274,16 +1274,6 @@ func init() { HEADLESSRequestDoc.Fields[6].Note = "" HEADLESSRequestDoc.Fields[6].Description = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found." HEADLESSRequestDoc.Fields[6].Comments[encoder.LineComment] = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found." - HEADLESSRequestDoc.Fields[7].Name = "fuzzing" - HEADLESSRequestDoc.Fields[7].Type = "[]fuzz.Rule" - HEADLESSRequestDoc.Fields[7].Note = "" - HEADLESSRequestDoc.Fields[7].Description = "Fuzzing describes schema to fuzz headless requests" - HEADLESSRequestDoc.Fields[7].Comments[encoder.LineComment] = " Fuzzing describes schema to fuzz headless requests" - HEADLESSRequestDoc.Fields[8].Name = "cookie-reuse" - HEADLESSRequestDoc.Fields[8].Type = "bool" - HEADLESSRequestDoc.Fields[8].Note = "" - HEADLESSRequestDoc.Fields[8].Description = "CookieReuse is an optional setting that enables cookie reuse" - HEADLESSRequestDoc.Fields[8].Comments[encoder.LineComment] = "CookieReuse is an optional setting that enables cookie reuse" ENGINEActionDoc.Type = "engine.Action" ENGINEActionDoc.Comments[encoder.LineComment] = " Action is an action taken by the browser to reach a navigation" diff --git a/v2/pkg/templates/tests/multiproto.json b/v2/pkg/templates/tests/multiproto.json new file mode 100644 index 00000000..41f53129 --- /dev/null +++ b/v2/pkg/templates/tests/multiproto.json @@ -0,0 +1,41 @@ +{ + "id": "nuclei-multi-protocol", + "info": { + "name": "multi protocol support", + "author": "pdteam", + "severity": "info" + }, + "dns": [ + { + "name": "{{FQDN}}", + "type": "cname" + } + ], + "ssl": [ + { + "address": "{{Hostname}}" + } + ], + "http": [ + { + "method": "GET", + "path": [ + "{{BaseURL}}" + ], + "headers": { + "Host": "{{ssl_subject_cn}}", + "Metadata": "{{ssl_cipher}}" + }, + "matchers": [ + { + "type": "dsl", + "dsl": [ + "http_status_code == 404", + "contains(dns_cname, 'github.io')" + ], + "condition": "and" + } + ] + } + ] +} \ No newline at end of file diff --git a/v2/pkg/templates/tests/multiproto.yaml b/v2/pkg/templates/tests/multiproto.yaml new file mode 100644 index 00000000..5e8754bd --- /dev/null +++ b/v2/pkg/templates/tests/multiproto.yaml @@ -0,0 +1,30 @@ +id: nuclei-multi-protocol + +info: + name: multi protocol support + author: pdteam + severity: info + +dns: + - name: "{{FQDN}}" # dns request + type: cname + +ssl: + - address: "{{Hostname}}" # ssl request + +http: + - method: GET + path: + - "{{BaseURL}}" # http request + + headers: + Host: "{{ssl_subject_cn}}" # host extracted from ssl request + Metadata: "{{ssl_cipher}}" + + matchers: + - type: dsl + dsl: + # - contains(http_body,'File not found') # check for http string + - http_status_code == 404 + - contains(dns_cname, 'github.io') # check for cname + condition: and \ No newline at end of file diff --git a/v2/pkg/templates/types/types.go b/v2/pkg/templates/types/types.go index d2366ae4..b51fd2bd 100644 --- a/v2/pkg/templates/types/types.go +++ b/v2/pkg/templates/types/types.go @@ -23,6 +23,7 @@ const ( FileProtocol // name:http HTTPProtocol + // name:offline-http OfflineHTTPProtocol // name:headless HeadlessProtocol @@ -36,22 +37,28 @@ const ( WebsocketProtocol // name:whois WHOISProtocol + // name:code + CodeProtocol + // name: js + JavascriptProtocol limit InvalidProtocol ) // ExtractorTypes is a table for conversion of extractor type from string. var protocolMappings = map[ProtocolType]string{ - InvalidProtocol: "invalid", - DNSProtocol: "dns", - FileProtocol: "file", - HTTPProtocol: "http", - HeadlessProtocol: "headless", - NetworkProtocol: "tcp", - WorkflowProtocol: "workflow", - SSLProtocol: "ssl", - WebsocketProtocol: "websocket", - WHOISProtocol: "whois", + InvalidProtocol: "invalid", + DNSProtocol: "dns", + FileProtocol: "file", + HTTPProtocol: "http", + HeadlessProtocol: "headless", + NetworkProtocol: "tcp", + WorkflowProtocol: "workflow", + SSLProtocol: "ssl", + WebsocketProtocol: "websocket", + WHOISProtocol: "whois", + CodeProtocol: "code", + JavascriptProtocol: "js", } func GetSupportedProtocolTypes() ProtocolTypes { diff --git a/v2/pkg/testutils/integration.go b/v2/pkg/testutils/integration.go index 79cad7e6..5156291c 100644 --- a/v2/pkg/testutils/integration.go +++ b/v2/pkg/testutils/integration.go @@ -37,7 +37,7 @@ func RunNucleiAndGetResults(isTemplate bool, template, url string, debug bool, e templateOrWorkflowFlag = "-w" } - return RunNucleiBareArgsAndGetResults(debug, append([]string{ + return RunNucleiBareArgsAndGetResults(debug, nil, append([]string{ templateOrWorkflowFlag, template, "-target", @@ -45,7 +45,7 @@ func RunNucleiAndGetResults(isTemplate bool, template, url string, debug bool, e }, extra...)...) } -func RunNucleiBareArgsAndGetResults(debug bool, extra ...string) ([]string, error) { +func RunNucleiBareArgsAndGetResults(debug bool, env []string, extra ...string) ([]string, error) { cmd := exec.Command("./nuclei") extra = append(extra, ExtraDebugArgs...) cmd.Args = append(cmd.Args, extra...) @@ -53,6 +53,9 @@ func RunNucleiBareArgsAndGetResults(debug bool, extra ...string) ([]string, erro cmd.Args = append(cmd.Args, "-interactions-poll-duration", "1") cmd.Args = append(cmd.Args, "-interactions-cooldown-period", "10") cmd.Args = append(cmd.Args, "-allow-local-file-access") + if env != nil { + cmd.Env = append(os.Environ(), env...) + } if debug { cmd.Args = append(cmd.Args, "-debug") cmd.Stderr = os.Stderr @@ -134,6 +137,40 @@ func RunNucleiArgsAndGetErrors(debug bool, env []string, extra ...string) ([]str return results, err } +// RunNucleiArgsWithEnvAndGetErrors returns a list of errors in nuclei output (ERR,WRN,FTL) +func RunNucleiArgsWithEnvAndGetResults(debug bool, env []string, extra ...string) ([]string, error) { + cmd := exec.Command("./nuclei") + extra = append(extra, ExtraDebugArgs...) + cmd.Env = append(os.Environ(), env...) + cmd.Args = append(cmd.Args, extra...) + cmd.Args = append(cmd.Args, "-duc") // disable auto updates + cmd.Args = append(cmd.Args, "-interactions-poll-duration", "1") + cmd.Args = append(cmd.Args, "-interactions-cooldown-period", "10") + cmd.Args = append(cmd.Args, "-allow-local-file-access") + if debug { + cmd.Args = append(cmd.Args, "-debug") + cmd.Stderr = os.Stderr + fmt.Println(cmd.String()) + } else { + cmd.Args = append(cmd.Args, "-silent") + } + data, err := cmd.Output() + if debug { + fmt.Println(string(data)) + } + if len(data) < 1 && err != nil { + return nil, fmt.Errorf("%v: %v", err.Error(), string(data)) + } + var parts []string + items := strings.Split(string(data), "\n") + for _, i := range items { + if i != "" { + parts = append(parts, i) + } + } + return parts, nil +} + var templateLoaded = regexp.MustCompile(`(?:Templates|Workflows) loaded[^:]*: (\d+)`) // RunNucleiBinaryAndGetLoadedTemplates returns a list of results for a template diff --git a/v2/pkg/testutils/testutils.go b/v2/pkg/testutils/testutils.go index d773ca33..43cff4df 100644 --- a/v2/pkg/testutils/testutils.go +++ b/v2/pkg/testutils/testutils.go @@ -78,7 +78,7 @@ type TemplateInfo struct { // NewMockExecuterOptions creates a new mock executeroptions struct func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions { - progressImpl, _ := progress.NewStatsTicker(0, false, false, false, false, 0) + progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) executerOpts := &protocols.ExecutorOptions{ TemplateID: info.ID, TemplateInfo: info.Info, @@ -92,6 +92,7 @@ func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protoco Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), } + executerOpts.CreateTemplateCtxStore() return executerOpts } @@ -105,6 +106,7 @@ func (n *NoopWriter) Write(data []byte, level levels.Level) {} type MockOutputWriter struct { aurora aurora.Aurora RequestCallback func(templateID, url, requestType string, err error) + FailureCallback func(result *output.InternalEvent) WriteCallback func(o *output.ResultEvent) } diff --git a/v2/pkg/tmplexec/README.md b/v2/pkg/tmplexec/README.md new file mode 100644 index 00000000..26e80daf --- /dev/null +++ b/v2/pkg/tmplexec/README.md @@ -0,0 +1,11 @@ +# tmplexec + +tmplexec also known as template executer executes template it is different from `protocols` package which only contains logic within the scope of one protocol. tmplexec is resposible for executing `Template` with defined logic. with introduction of `multi protocol` and `flow` templates (deprecated package protocols/common/executer) did not seem appropriate/helpful anymore as it is outside of protocol scope and deals with execution of template which can contain 1 requests , or multiple requests of same protocol or multiple requests of different protocols. tmplexec is responsible for executing template and handling all logic related to it. + +## Engine/Backends + +Currently there are 3 engines for template execution + +- `Generic` => executes request[s] of same/one protocol +- `MultiProtocol` => executes requests of multiple protocols with shared logic between protocol requests see [multiprotocol](multiproto/README.md) +- `Flow` => executes requests of one or multiple protocol requests as specified by template in javascript (aka flow) [flow](flow/README.md) \ No newline at end of file diff --git a/v2/pkg/tmplexec/doc.go b/v2/pkg/tmplexec/doc.go new file mode 100644 index 00000000..88207b9e --- /dev/null +++ b/v2/pkg/tmplexec/doc.go @@ -0,0 +1,5 @@ +package tmplexec + +// tmplexec is package that provides +// template executors it is one level higher than protocols +// and deals with execution of entire template diff --git a/v2/pkg/tmplexec/exec.go b/v2/pkg/tmplexec/exec.go new file mode 100644 index 00000000..08234e1b --- /dev/null +++ b/v2/pkg/tmplexec/exec.go @@ -0,0 +1,164 @@ +package tmplexec + +import ( + "errors" + "fmt" + "strings" + "sync/atomic" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/writer" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/flow" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/generic" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/multiproto" +) + +// TemplateExecutor is an executor for a template +type TemplateExecuter struct { + requests []protocols.Request + options *protocols.ExecutorOptions + engine TemplateEngine + results *atomic.Bool +} + +// Both executer & Executor are correct spellings (its open to interpretation) + +var _ protocols.Executer = &TemplateExecuter{} + +// NewTemplateExecuter creates a new request TemplateExecuter for list of requests +func NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) *TemplateExecuter { + isMultiProto := false + lastProto := "" + for _, request := range requests { + if request.Type().String() != lastProto && lastProto != "" { + isMultiProto = true + break + } + lastProto = request.Type().String() + } + + e := &TemplateExecuter{requests: requests, options: options, results: &atomic.Bool{}} + if options.Flow != "" { + // we use a dummy input here because goal of flow executor at this point is to just check + // syntax and other things are correct before proceeding to actual execution + // during execution new instance of flow will be created as it is tightly coupled with lot of executor options + e.engine = flow.NewFlowExecutor(requests, contextargs.NewWithInput("dummy"), options, e.results) + } else { + // Review: + // multiproto engine is only used if there is more than one protocol in template + // else we use generic engine (should we use multiproto engine for single protocol with multiple requests as well ?) + if isMultiProto { + e.engine = multiproto.NewMultiProtocol(requests, options, e.results) + } else { + e.engine = generic.NewGenericEngine(requests, options, e.results) + } + } + + return e +} + +// Compile compiles the execution generators preparing any requests possible. +func (e *TemplateExecuter) Compile() error { + cliOptions := e.options.Options + + for _, request := range e.requests { + if err := request.Compile(e.options); err != nil { + var dslCompilationError *dsl.CompilationError + if errors.As(err, &dslCompilationError) { + if cliOptions.Verbose { + rawErrorMessage := dslCompilationError.Error() + formattedErrorMessage := strings.ToUpper(rawErrorMessage[:1]) + rawErrorMessage[1:] + "." + gologger.Warning().Msgf(formattedErrorMessage) + gologger.Info().Msgf("The available custom DSL functions are:") + fmt.Println(dsl.GetPrintableDslFunctionSignatures(cliOptions.NoColor)) + } + } + return err + } + } + return e.engine.Compile() +} + +// Requests returns the total number of requests the rule will perform +func (e *TemplateExecuter) Requests() int { + var count int + for _, request := range e.requests { + count += request.Requests() + } + return count +} + +// Execute executes the protocol group and returns true or false if results were found. +func (e *TemplateExecuter) Execute(input *contextargs.Context) (bool, error) { + results := &atomic.Bool{} + defer func() { + // it is essential to remove template context of `Scan i.e template x input pair` + // since it is of no use after scan is completed (regardless of success or failure) + e.options.RemoveTemplateCtx(input.MetaInput) + }() + + var lastMatcherEvent *output.InternalWrappedEvent + writeFailureCallback := func(event *output.InternalWrappedEvent, matcherStatus bool) { + if !results.Load() && matcherStatus { + if err := e.options.Output.WriteFailure(event); err != nil { + gologger.Warning().Msgf("Could not write failure event to output: %s\n", err) + } + results.CompareAndSwap(false, true) + } + } + + cliExecutorCallback := func(event *output.InternalWrappedEvent) { + if event == nil { + // something went wrong + return + } + // If no results were found, and also interactsh is not being used + // in that case we can skip it, otherwise we've to show failure in + // case of matcher-status flag. + if !event.HasOperatorResult() && !event.UsesInteractsh { + lastMatcherEvent = event + } else { + if writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient) { + results.CompareAndSwap(false, true) + } else { + lastMatcherEvent = event + } + } + } + var err error + + // Note: this is required for flow executor + // flow executer is tightly coupled with lot of executor options + // and map , wg and other types earlier we tried to use (compile once and run multiple times) + // but it is causing lot of panic and nil pointer dereference issues + // so in compile step earlier we compile it to validate javascript syntax and other things + // and while executing we create new instance of flow executor everytime + if e.options.Flow != "" { + flowexec := flow.NewFlowExecutor(e.requests, input, e.options, results) + if err := flowexec.Compile(); err != nil { + return false, err + } + err = flowexec.ExecuteWithResults(input, cliExecutorCallback) + } else { + err = e.engine.ExecuteWithResults(input, cliExecutorCallback) + } + + if lastMatcherEvent != nil { + writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus) + } + return results.Load(), err +} + +// ExecuteWithResults executes the protocol requests and returns results instead of writing them. +func (e *TemplateExecuter) ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error { + userCallback := func(event *output.InternalWrappedEvent) { + if event != nil { + callback(event) + } + } + return e.engine.ExecuteWithResults(input, userCallback) +} diff --git a/v2/pkg/tmplexec/flow/README.md b/v2/pkg/tmplexec/flow/README.md new file mode 100644 index 00000000..0b4fc498 --- /dev/null +++ b/v2/pkg/tmplexec/flow/README.md @@ -0,0 +1,319 @@ +# flow + +flow is a new template engine/backend introduced in v3 which primarily adds 2 most awaited features +- conditional execution of requests (ex: `flow: dns() && http()`) +- request execution orchestration (iterate over a slice, request execution order, if/for statement) + +both of these features are implemented using javascript (ECMAScript 5.1) with [goja](https://github.com/dop251/goja) backend. +## conditional execution + +Many times when writing complex templates we might need to add some extra checks (or conditional statements) before executing certain part of request + +An ideal example of this would be when bruteforcing wordpress login with default usernames and passwords. If we try to write a template for this it would be something like this +```yaml +id: wordpress-bruteforce +info: + name: WordPress Login Bruteforce + author: pdteam + severity: high + +http: + - method: POST + path: + - "{{BaseURL}}/wp-login.php" + payloads: + username: + - admin + - guest + - testuser + password: + - password123 + - qwertyuiop + - letmein + body: "log=§username§&pwd=§password§&wp-submit=Log+In" + attack: clusterbomb + matchers: + - type: word + words: + - "ERROR" + part: body + negative: true +``` + +but if we carefully re-evaluate this template, we can see that template is sending 9 requests without even checking, if the url actually exists or target site is actually a wordpress site. before v3 it was possible to do this by adding a extractor and sending additional content in say url fragment and it would fail if request was not successful and another way would be writing a workflow (2 templates and 1 workflow file i.e total 3 files for 1 template) but this is `hacky` and not a good solution. + +With addition of flow in Nuclei v3 we can re-write this template to first check if target is a wordpress site, if yes then bruteforce login with default credentials and this can be achieved by simply adding one line of content i.e `flow: http("check-wp") && http("bruteforce")` and nuclei will take care of everything else. + +```yaml +id: wordpress-bruteforce +info: + name: WordPress Login Bruteforce + author: pdteam + severity: high + +flow: http("check-wp") && http("bruteforce") + +http: + - id: check-wp + method: GET + path: + - "{{BaseURL}}/wp-login.php" + + matchers: + - type: word + words: + - "WordPress" + part: body + - type: word + words: + - "wp-content" + part: body + matchers-condition: and + + - id: bruteforce + method: POST + path: + - "{{BaseURL}}/wp-login.php" + payloads: + username: + - admin + - guest + - testuser + password: + - password123 + - qwertyuiop + - letmein + body: "log=§username§&pwd=§password§&wp-submit=Log+In" + attack: clusterbomb + matchers: + - type: word + words: + - "ERROR" + part: body + negative: true +``` +**Note:** this is just a example template with poor matchers. refer 'nuclei-templates' repo for final template + +The update template now seems straight forward and easy to understand. we are first checking if target is a wordpress site and then executing bruteforce requests. This is just a simple example of conditional execution and flow accepts any Javascript (ECMAScript 5.1) expression/code so you are free to craft any conditional execution logic you want using for,if and whatnot. + +## request execution orchestration + +`conditional execution` is one simple use case of flow but `flow` is much more powerful than that, for example it can be used to +- iterate over a slice of values and execute requests for each value (ex: [dns-flow-probe](testcases/nuclei-flow-dns.yaml)) +- extract values from one request and iterate over them and execute requests for each value ex: [dns-flow-probe](testcases/nuclei-flow-dns.yaml) +- get/set values from/to template context (global variables) +- print/log values to stdout at xyz condition or while debugging +- adding custom logic during template execution (ex: if status code is 403 => login and then re-run it) +- use any/all ECMAScript 5.1 javascript (like objects,arrays etc) and build/transform variables/input at runtime +- update variables at runtime (ex: when jwt expires update it by using refresh token and then continue execution) +- and a lot more (this is just a tip of iceberg) + +simply put request execution orchestration can be understood as nuclei logic bindings for javascript (i.e two way interaction between javascript and nuclei for a specific template) + +To better understand orchestration we can try to build a template for vhost enumeration using flow. which usually requires writing / using a new tool + +**for basic vhost enumeration a template should** +- do a PTR lookup for given ip +- get SSL ceritificate for given ip (i.e tls-grab) + - extract subject_cn from certificate + - extract subject_alt_names(SAN) from certificate + - filter out wildcard prefix from above values +- and finally bruteforce all found vhosts + + +**Now if we try to implement this in template it would be** +```yaml +# send a ssl request to get certificate +ssl: + - address: "{{Host}}:{{Port}}" + +# do a PTR lookup for given ip and get PTR value +dns: + - name: "{{FQDN}}" + type: PTR + + matchers: + - type: word + words: + - "IN\tPTR" + + extractors: + - type: regex + name: ptrValue + internal: true + group: 1 + regex: + - "IN\tPTR\t(.+)" + +# bruteforce all found vhosts +http: + - raw: + - | + GET / HTTP/1.1 + Host: {{vhost}} + + matchers: + - type: status + negative: true + status: + - 400 + - 502 + + extractors: + - type: dsl + dsl: + - '"VHOST: " + vhost + ", SC: " + status_code + ", CL: " + content_length' tarun@macbook:~/Codebase/nuclei/integration_tes +``` +**But this template is not yet ready as it is missing core logic i.e how we use all these obtained data and do bruteforce** +and this is where flow comes into picture. flow is javascript code with two way bindings to nuclei. if we write javascript code to orchestrate vhost enumeration it is as simple as +```javascript + ssl(); + dns(); + for (let vhost of iterate(template["ssl_subject_cn"],template["ssl_subject_an"])) { + set("vhost", vhost); + http(); } +``` + +With just extra 5 lines of javascript code template can now perform vhost enumeration and this can be run on scale with all awesome features of nuclei with various supported inputs like ASN,CIDR,URL etc + + +In above Js code we are using some Nuclei JS bindings lets understand what they do + +- `ssl()` => execute ssl request +- `dns()` => execute dns request +- `template["ssl_subject_cn"]` => get value of `ssl_subject_cn` from template context (global variables) +- `iterate()` => this is a nuclei helper function which iterates any type of value (array,map,string,number) while handling empty / nil values +- `set("vhost",vhost)` => creates new variable `vhost` in template and assigns value of `vhost` to it +- `http()` => execute http request + + +This template is now missing one last thing i.e +- removing wildcard prefix (*.) in subject_cn,subject_an +- trailing `.` in PTR value + +and this can be done using either JS methods of using DSL helper functions as shown in below template + +```yaml +id: vhost-enum-flow + +info: + name: vhost enum flow + author: tarunKoyalwar + severity: info + description: | + vhost enumeration by extracting potential vhost names from ssl certificate and dns ptr records + +flow: | + ssl(); + dns({hide: true}); + for (let vhost of iterate(template["ssl_subject_cn"],template["ssl_subject_an"])) { + vhost = vhost.replace("*.", "") + set("vhost", vhost); + http(); + } + +ssl: + - address: "{{Host}}:{{Port}}" + +dns: + - name: "{{FQDN}}" + type: PTR + + matchers: + - type: word + words: + - "IN\tPTR" + + extractors: + - type: regex + name: ptrValue + internal: true + group: 1 + regex: + - "IN\tPTR\t(.+)" + +http: + - raw: + - | + GET / HTTP/1.1 + Host: {{trim_suffix(vhost, ".")}} + + matchers: + - type: status + negative: true + status: + - 400 + - 502 + + extractors: + - type: dsl + dsl: + - '"VHOST: " + vhost + ", SC: " + status_code + ", CL: " + content_length' +``` + + +### Nuclei JS Bindings + +This section contains a brief description of all nuclei JS bindings and their usage + +**1. Protocol Execution Functions** + + Any protocol that is present in a nuclei template can be called/executed in javascript in format `proto_name()` i.e `http()` , `dns()` , `ssl()` etc + If we want to execute a specific request of a protocol (ref: see [nuclei-flow-dns](testcases/nuclei-flow-dns-id.yaml)) this can be achieved by either passing + - index of that request in protocol (ex: `dns(0)`, `dns(1)` etc) + - id of that request in protocol (ex: `dns("extract-vps")`, `dns("probe-http")` etc) + For More complex use cases multiple requests of a single protocol can be executed by just specifying their index or id one after another (ex: `dns("extract-vps","1")`) + +**2. Iterate Helper Function** + + Iterate is a nuclei js helper function which can be used to iterate over any type of value (array,map,string,number) while handling empty / nil values. + This is addon helper function from nuclei to omit boilerplate code of checking if value is empty or not and then iterating over it + ```javascript + iterate(123,{"a":1,"b":2,"c":3}) + // iterate over array with custom separator + iterate([1,2,3,4,5], " ") + ``` + **Note:** In above example we used `iterate(template["ssl_subject_cn"],template["ssl_subject_an"])` which removed lot of boilerplate code of checking if value is empty or not and then iterating over it + +**3. Set Helper Function** + + When Iterating over a values/array or some other use case we might want to invoke a request with custom/given value and this can be achieved by using `set()` helper function. When invoked/called it adds given variable to template context (global variables) and that value is used during execution of request/protocol. the format of `set()` is `set("variable_name",value)` ex: `set("username","admin")` etc + ```javascript + for (let vhost of myArray) { + set("vhost", vhost); + http(1) + } + ``` + **Note:** In above example we used `set("vhost", vhost)` which added `vhost` to template context (global variables) and then called `http(1)` which used this value in request + +**4. Template Context** + + when using `nuclei -jsonl` flag we get lot of data/metadata related to a vulnerability (ex: template details,extracted-values and much more) . A template context is nothing but a map/JSON containing all this data along with internal/unexported data that is only available at runtime (ex: extracted values from previous requests, variables added using `set()` etc). This template context is available in javascript as `template` variable and can be used to access any data from it. ex: `template["ssl_subject_cn"]` , `template["ssl_subject_an"]` etc + ```javascript + template["ssl_subject_cn"] // returns value of ssl_subject_cn from template context which is available after executing ssl request + template["ptrValue"] // returns value of ptrValue which was extracted using regex with internal: true + ``` + Lot of times we don't known what all data is available in template context and this can be easily found by printing it to stdout using `log()` function + ```javascript + log(template) + ``` + +**5. Log Helper Function** + + It is a nuclei js alternative to `console.log` and this pretty prints map data in readable format + **Note:** This should be used for debugging purposed only as this prints data to stdout + +**6. Dedupe** + + Lot of times just having arrays/slices is not enough and we might need to remove duplicate variables . for example in earlier vhost enumeration we did not remove any duplicates as there is always a chance of duplicate values in `ssl_subject_cn` and `ssl_subject_an` and this can be achieved by using `dedupe()` object. This is nuclei js helper function to abstract away boilerplate code of removing duplicates from array/slice + ```javascript + let uniq = new Dedupe(); // create new dedupe object + uniq.Add(template["ptrValue"]) + uniq.Add(template["ssl_subject_cn"]); + uniq.Add(template["ssl_subject_an"]); + log(uniq.Values()) + ``` + And that's it , this automatically converts any slice/array to map and removes duplicates from it and returns a slice/array of unique values + +------ +> Similar to DSL helper functions . we can either use built in functions available with `Javscript (ECMAScript 5.1)` or use DSL helper functions and its upto user to decide which one to uses \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/builtin/dedupe.go b/v2/pkg/tmplexec/flow/builtin/dedupe.go new file mode 100644 index 00000000..eae088db --- /dev/null +++ b/v2/pkg/tmplexec/flow/builtin/dedupe.go @@ -0,0 +1,67 @@ +package builtin + +import ( + "crypto/md5" + "reflect" + + "github.com/dop251/goja" + "github.com/projectdiscovery/nuclei/v2/pkg/types" +) + +// Dedupe is a javascript builtin for deduping values +type Dedupe struct { + m map[string]goja.Value + VM *goja.Runtime +} + +// Add adds a value to the dedupe +func (d *Dedupe) Add(call goja.FunctionCall) goja.Value { + allVars := []any{} + for _, v := range call.Arguments { + if v.Export() == nil { + continue + } + if v.ExportType().Kind() == reflect.Slice { + // convert []datatype to []interface{} + // since it cannot be type asserted to []interface{} directly + rfValue := reflect.ValueOf(v.Export()) + for i := 0; i < rfValue.Len(); i++ { + allVars = append(allVars, rfValue.Index(i).Interface()) + } + } else { + allVars = append(allVars, v.Export()) + } + } + for _, v := range allVars { + hash := hashValue(v) + if _, ok := d.m[hash]; ok { + continue + } + d.m[hash] = d.VM.ToValue(v) + } + return d.VM.ToValue(true) +} + +// Values returns all values from the dedupe +func (d *Dedupe) Values(call goja.FunctionCall) goja.Value { + tmp := []goja.Value{} + for _, v := range d.m { + tmp = append(tmp, v) + } + return d.VM.ToValue(tmp) +} + +// NewDedupe creates a new dedupe builtin object +func NewDedupe(vm *goja.Runtime) *Dedupe { + return &Dedupe{ + m: make(map[string]goja.Value), + VM: vm, + } +} + +// hashValue returns a hash of the value +func hashValue(value interface{}) string { + res := types.ToString(value) + md5sum := md5.Sum([]byte(res)) + return string(md5sum[:]) +} diff --git a/v2/pkg/tmplexec/flow/doc.go b/v2/pkg/tmplexec/flow/doc.go new file mode 100644 index 00000000..42882657 --- /dev/null +++ b/v2/pkg/tmplexec/flow/doc.go @@ -0,0 +1 @@ +package flow diff --git a/v2/pkg/tmplexec/flow/flow_executor.go b/v2/pkg/tmplexec/flow/flow_executor.go new file mode 100644 index 00000000..8cff7d97 --- /dev/null +++ b/v2/pkg/tmplexec/flow/flow_executor.go @@ -0,0 +1,241 @@ +package flow + +import ( + "fmt" + "io" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/dop251/goja" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" + + "github.com/projectdiscovery/nuclei/v2/pkg/types" + errorutil "github.com/projectdiscovery/utils/errors" + fileutil "github.com/projectdiscovery/utils/file" + mapsutil "github.com/projectdiscovery/utils/maps" + "go.uber.org/multierr" +) + +var ( + // ErrInvalidRequestID is a request id error + ErrInvalidRequestID = errorutil.NewWithFmt("[%s] invalid request id '%s' provided") +) + +// FlowExecutor is a flow executor for executing a flow +type FlowExecutor struct { + input *contextargs.Context + options *protocols.ExecutorOptions + + // javascript runtime reference and compiled program + jsVM *goja.Runtime + program *goja.Program // compiled js program + + // protocol requests and their callback functions + allProtocols map[string][]protocols.Request + protoFunctions map[string]func(call goja.FunctionCall) goja.Value // reqFunctions contains functions that allow executing requests/protocols from js + callback func(event *output.InternalWrappedEvent) // result event callback + + // logic related variables + wg sync.WaitGroup + results *atomic.Bool + allErrs mapsutil.SyncLockMap[string, error] +} + +// NewFlowExecutor creates a new flow executor from a list of requests +func NewFlowExecutor(requests []protocols.Request, input *contextargs.Context, options *protocols.ExecutorOptions, results *atomic.Bool) *FlowExecutor { + allprotos := make(map[string][]protocols.Request) + for _, req := range requests { + switch req.Type() { + case templateTypes.DNSProtocol: + allprotos[templateTypes.DNSProtocol.String()] = append(allprotos[templateTypes.DNSProtocol.String()], req) + case templateTypes.HTTPProtocol: + allprotos[templateTypes.HTTPProtocol.String()] = append(allprotos[templateTypes.HTTPProtocol.String()], req) + case templateTypes.NetworkProtocol: + allprotos[templateTypes.NetworkProtocol.String()] = append(allprotos[templateTypes.NetworkProtocol.String()], req) + case templateTypes.FileProtocol: + allprotos[templateTypes.FileProtocol.String()] = append(allprotos[templateTypes.FileProtocol.String()], req) + case templateTypes.HeadlessProtocol: + allprotos[templateTypes.HeadlessProtocol.String()] = append(allprotos[templateTypes.HeadlessProtocol.String()], req) + case templateTypes.SSLProtocol: + allprotos[templateTypes.SSLProtocol.String()] = append(allprotos[templateTypes.SSLProtocol.String()], req) + case templateTypes.WebsocketProtocol: + allprotos[templateTypes.WebsocketProtocol.String()] = append(allprotos[templateTypes.WebsocketProtocol.String()], req) + case templateTypes.WHOISProtocol: + allprotos[templateTypes.WHOISProtocol.String()] = append(allprotos[templateTypes.WHOISProtocol.String()], req) + case templateTypes.CodeProtocol: + allprotos[templateTypes.CodeProtocol.String()] = append(allprotos[templateTypes.CodeProtocol.String()], req) + default: + gologger.Error().Msgf("invalid request type %s", req.Type().String()) + } + } + f := &FlowExecutor{ + allProtocols: allprotos, + options: options, + allErrs: mapsutil.SyncLockMap[string, error]{ + ReadOnly: atomic.Bool{}, + Map: make(map[string]error), + }, + protoFunctions: map[string]func(call goja.FunctionCall) goja.Value{}, + results: results, + jsVM: protocolstate.NewJSRuntime(), + input: input, + } + return f +} + +// Compile compiles js program and registers all functions +func (f *FlowExecutor) Compile() error { + if f.results == nil { + f.results = new(atomic.Bool) + } + // load all variables and evaluate with existing data + variableMap := f.options.Variables.Evaluate(f.options.GetTemplateCtx(f.input.MetaInput).GetAll()) + // cli options + optionVars := generators.BuildPayloadFromOptions(f.options.Options) + // constants + constants := f.options.Constants + allVars := generators.MergeMaps(variableMap, constants, optionVars) + // we support loading variables from files in variables , cli options and constants + // try to load if files exist + for k, v := range allVars { + if str, ok := v.(string); ok && len(str) < 150 && fileutil.FileExists(str) { + if value, err := f.ReadDataFromFile(str); err == nil { + allVars[k] = value + } else { + gologger.Warning().Msgf("could not load file '%s' for variable '%s': %s", str, k, err) + } + } + } + f.options.GetTemplateCtx(f.input.MetaInput).Merge(allVars) // merge all variables into template context + + // ---- define callback functions/objects---- + f.protoFunctions = map[string]func(call goja.FunctionCall) goja.Value{} + // iterate over all protocols and generate callback functions for each protocol + for p, requests := range f.allProtocols { + // for each protocol build a requestMap with reqID and protocol request + reqMap := mapsutil.Map[string, protocols.Request]{} + counter := 0 + proto := strings.ToLower(p) // donot use loop variables in callback functions directly + for index := range requests { + counter++ // start index from 1 + request := f.allProtocols[proto][index] + if request.GetID() != "" { + // if id is present use it + reqMap[request.GetID()] = request + } + // fallback to using index as id + // always allow index as id as a fallback + reqMap[strconv.Itoa(counter)] = request + } + // ---define hook that allows protocol/request execution from js----- + // --- this is the actual callback that is executed when function is invoked in js---- + f.protoFunctions[proto] = func(call goja.FunctionCall) goja.Value { + opts := &ProtoOptions{ + protoName: proto, + } + for _, v := range call.Arguments { + switch value := v.Export().(type) { + case map[string]interface{}: + opts.LoadOptions(value) + default: + opts.reqIDS = append(opts.reqIDS, types.ToString(value)) + } + } + // parallel execution of protocols + if opts.Async { + f.wg.Add(1) + go func() { + defer f.wg.Done() + f.requestExecutor(reqMap, opts) + }() + return f.jsVM.ToValue(true) + } + + return f.jsVM.ToValue(f.requestExecutor(reqMap, opts)) + } + } + return f.registerBuiltInFunctions() +} + +// ExecuteWithResults executes the flow and returns results +func (f *FlowExecutor) ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error { + defer func() { + if e := recover(); e != nil { + gologger.Error().Label(f.options.TemplateID).Msgf("panic occurred while executing target %v with flow: %v", input.MetaInput.Input, e) + panic(e) + } + }() + + f.callback = callback + f.input = input + // -----Load all types of variables----- + // add all input args to template context + if f.input != nil && f.input.HasArgs() { + f.input.ForEach(func(key string, value interface{}) { + f.options.GetTemplateCtx(f.input.MetaInput).Set(key, value) + }) + } + if f.callback == nil { + return fmt.Errorf("output callback cannot be nil") + } + // pass flow and execute the js vm and handle errors + value, err := f.jsVM.RunProgram(f.program) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to execute flow\n%v\n", f.options.Flow) + } + f.wg.Wait() + runtimeErr := f.GetRuntimeErrors() + if runtimeErr != nil { + return errorutil.NewWithErr(runtimeErr).Msgf("got following errors while executing flow") + } + if value.Export() != nil { + f.results.Store(value.ToBoolean()) + } else { + f.results.Store(true) + } + return nil +} + +// GetRuntimeErrors returns all runtime errors (i.e errors from all protocol combined) +func (f *FlowExecutor) GetRuntimeErrors() error { + errs := []error{} + for proto, err := range f.allErrs.GetAll() { + errs = append(errs, errorutil.NewWithErr(err).Msgf("failed to execute %v protocol", proto)) + } + return multierr.Combine(errs...) +} + +// ReadDataFromFile reads data from file respecting sandbox options +func (f *FlowExecutor) ReadDataFromFile(payload string) ([]string, error) { + values := []string{} + // load file respecting sandbox + reader, err := f.options.Options.LoadHelperFile(payload, f.options.TemplatePath, f.options.Catalog) + if err != nil { + return values, err + } + defer reader.Close() + bin, err := io.ReadAll(reader) + if err != nil { + return values, err + } + for _, line := range strings.Split(string(bin), "\n") { + line = strings.TrimSpace(line) + if line != "" { + values = append(values, line) + } + } + return values, nil +} + +// Name returns the type of engine +func (f *FlowExecutor) Name() string { + return "flow" +} diff --git a/v2/pkg/tmplexec/flow/flow_executor_test.go b/v2/pkg/tmplexec/flow/flow_executor_test.go new file mode 100644 index 00000000..5680735b --- /dev/null +++ b/v2/pkg/tmplexec/flow/flow_executor_test.go @@ -0,0 +1,173 @@ +package flow_test + +import ( + "context" + "log" + "testing" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/parsers" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" + "github.com/projectdiscovery/ratelimit" + "github.com/stretchr/testify/require" +) + +var executerOpts protocols.ExecutorOptions + +func setup() { + options := testutils.DefaultOptions + testutils.Init(options) + progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) + + executerOpts = protocols.ExecutorOptions{ + Output: testutils.NewMockOutputWriter(), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + } + workflowLoader, err := parsers.NewLoader(&executerOpts) + if err != nil { + log.Fatalf("Could not create workflow loader: %s\n", err) + } + executerOpts.WorkflowLoader = workflowLoader +} + +func TestFlowTemplateWithIndex(t *testing.T) { + // test + setup() + Template, err := templates.Parse("testcases/nuclei-flow-dns.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + input := contextargs.NewWithInput("hackerone.com") + gotresults, err := Template.Executer.Execute(input) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} + +func TestFlowTemplateWithID(t *testing.T) { + setup() + // apart from parse->compile->execution this testcase checks support for use custom id for protocol request and invocation of + // the same in js + Template, err := templates.Parse("testcases/nuclei-flow-dns-id.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + target := contextargs.NewWithInput("hackerone.com") + gotresults, err := Template.Executer.Execute(target) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} + +func TestFlowWithProtoPrefix(t *testing.T) { + // test + setup() + + // apart from parse->compile->execution this testcase checks + // mix of custom protocol request id and index is supported in js + // and also validates availability of protocol response variables in template context + Template, err := templates.Parse("testcases/nuclei-flow-dns-prefix.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + input := contextargs.NewWithInput("hackerone.com") + gotresults, err := Template.Executer.Execute(input) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} + +func TestFlowWithConditionNegative(t *testing.T) { + setup() + + // apart from parse->compile->execution this testcase checks + // if bitwise operator (&&) are properly executed and working + Template, err := templates.Parse("testcases/condition-flow.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + input := contextargs.NewWithInput("scanme.sh") + // expect no results and verify thant dns request is executed and http is not + gotresults, err := Template.Executer.Execute(input) + require.Nil(t, err, "could not execute template") + require.False(t, gotresults) +} + +func TestFlowWithConditionPositive(t *testing.T) { + setup() + + // apart from parse->compile->execution this testcase checks + // if bitwise operator (&&) are properly executed and working + Template, err := templates.Parse("testcases/condition-flow.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + input := contextargs.NewWithInput("blog.projectdiscovery.io") + // positive match . expect results also verify that both dns() and http() were executed + gotresults, err := Template.Executer.Execute(input) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} + +func TestFlowWithNoMatchers(t *testing.T) { + // when using conditional flow with no matchers at all + // we implicitly assume that request was successful and internally changed the result to true (for scope of condition only) + + // testcase-1 : no matchers but contains extractor + Template, err := templates.Parse("testcases/condition-flow-extractors.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + // positive match . expect results also verify that both dns() and http() were executed + gotresults, err := Template.Executer.Execute(contextargs.NewWithInput("blog.projectdiscovery.io")) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) + + // testcase-2 : no matchers and no extractors + Template, err = templates.Parse("testcases/condition-flow-no-operators.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.True(t, Template.Flow != "", "not a flow template") // this is classifer if template is flow or not + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + // positive match . expect results also verify that both dns() and http() were executed + gotresults, err = Template.Executer.Execute(contextargs.NewWithInput("blog.projectdiscovery.io")) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) + +} diff --git a/v2/pkg/tmplexec/flow/flow_internal.go b/v2/pkg/tmplexec/flow/flow_internal.go new file mode 100644 index 00000000..3e9beb7b --- /dev/null +++ b/v2/pkg/tmplexec/flow/flow_internal.go @@ -0,0 +1,217 @@ +package flow + +import ( + "reflect" + "sync/atomic" + + "github.com/dop251/goja" + "github.com/logrusorgru/aurora" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/flow/builtin" + "github.com/projectdiscovery/nuclei/v2/pkg/types" + mapsutil "github.com/projectdiscovery/utils/maps" +) + +// contains all internal/unexported methods of flow + +// requestExecutor executes a protocol/request and returns true if any matcher was found +func (f *FlowExecutor) requestExecutor(reqMap mapsutil.Map[string, protocols.Request], opts *ProtoOptions) bool { + defer func() { + // evaluate all variables after execution of each protocol + variableMap := f.options.Variables.Evaluate(f.options.GetTemplateCtx(f.input.MetaInput).GetAll()) + f.options.GetTemplateCtx(f.input.MetaInput).Merge(variableMap) // merge all variables into template context + + // to avoid polling update template variables everytime we execute a protocol + var m map[string]interface{} = f.options.GetTemplateCtx(f.input.MetaInput).GetAll() + _ = f.jsVM.Set("template", m) + }() + matcherStatus := &atomic.Bool{} // due to interactsh matcher polling logic this needs to be atomic bool + // if no id is passed execute all requests in sequence + if len(opts.reqIDS) == 0 { + // execution logic for http()/dns() etc + for index := range f.allProtocols[opts.protoName] { + req := f.allProtocols[opts.protoName][index] + err := req.ExecuteWithResults(f.input, output.InternalEvent(f.options.GetTemplateCtx(f.input.MetaInput).GetAll()), nil, func(result *output.InternalWrappedEvent) { + if result != nil { + f.results.CompareAndSwap(false, true) + if !opts.Hide { + f.callback(result) + } + // export dynamic values from operators (i.e internal:true) + // add add it to template context + // this is a conflicting behaviour with iterate-all + if result.HasOperatorResult() { + matcherStatus.CompareAndSwap(false, result.OperatorsResult.Matched) + if !result.OperatorsResult.Matched && !hasMatchers(req.GetCompiledOperators()) { + // if matcher status is false . check if template/request contains any matcher at all + // if it does then we need to set matcher status to true + matcherStatus.CompareAndSwap(false, true) + } + if len(result.OperatorsResult.DynamicValues) > 0 { + for k, v := range result.OperatorsResult.DynamicValues { + f.options.GetTemplateCtx(f.input.MetaInput).Set(k, v) + } + } + } else if !result.HasOperatorResult() && !hasOperators(req.GetCompiledOperators()) { + // if matcher status is false . check if template/request contains any matcher at all + // if it does then we need to set matcher status to true + matcherStatus.CompareAndSwap(false, true) + } + } + }) + if err != nil { + // save all errors in a map with id as key + // its less likely that there will be race condition but just in case + id := req.GetID() + if id == "" { + id, _ = reqMap.GetKeyWithValue(req) + } + err = f.allErrs.Set(opts.protoName+":"+id, err) + if err != nil { + gologger.Error().Msgf("failed to store flow runtime errors got %v", err) + } + return matcherStatus.Load() + } + } + return matcherStatus.Load() + } + + // execution logic for http("0") or http("get-aws-vpcs") + for _, id := range opts.reqIDS { + req, ok := reqMap[id] + if !ok { + gologger.Error().Msgf("[%v] invalid request id '%s' provided", f.options.TemplateID, id) + // compile error + if err := f.allErrs.Set(opts.protoName+":"+id, ErrInvalidRequestID.Msgf(f.options.TemplateID, id)); err != nil { + gologger.Error().Msgf("failed to store flow runtime errors got %v", err) + } + return matcherStatus.Load() + } + err := req.ExecuteWithResults(f.input, output.InternalEvent(f.options.GetTemplateCtx(f.input.MetaInput).GetAll()), nil, func(result *output.InternalWrappedEvent) { + if result != nil { + f.results.CompareAndSwap(false, true) + if !opts.Hide { + f.callback(result) + } + // export dynamic values from operators (i.e internal:true) + // add add it to template context + if result.HasOperatorResult() { + matcherStatus.CompareAndSwap(false, result.OperatorsResult.Matched) + if len(result.OperatorsResult.DynamicValues) > 0 { + for k, v := range result.OperatorsResult.DynamicValues { + f.options.GetTemplateCtx(f.input.MetaInput).Set(k, v) + } + _ = f.jsVM.Set("template", f.options.GetTemplateCtx(f.input.MetaInput).GetAll()) + } + } + } + }) + if err != nil { + index := id + err = f.allErrs.Set(opts.protoName+":"+index, err) + if err != nil { + gologger.Error().Msgf("failed to store flow runtime errors got %v", err) + } + } + } + return matcherStatus.Load() +} + +// registerBuiltInFunctions registers all built in functions for the flow +func (f *FlowExecutor) registerBuiltInFunctions() error { + // currently we register following builtin functions + // log -> log to stdout with [JS] prefix should only be used for debugging + // set -> set a variable in template context + // proto(arg ...String) <- this is generic syntax of how a protocol/request binding looks in js + // we only register only those protocols that are available in template + + // we also register a map datatype called template with all template variables + // template -> all template variables are available in js template object + + if err := f.jsVM.Set("log", func(call goja.FunctionCall) goja.Value { + // TODO: verify string interpolation and handle multiple args + arg := call.Argument(0).Export() + switch value := arg.(type) { + case string: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value) + case map[string]interface{}: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), vardump.DumpVariables(value)) + default: + gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value) + } + return goja.Null() + }); err != nil { + return err + } + + if err := f.jsVM.Set("set", func(call goja.FunctionCall) goja.Value { + varName := call.Argument(0).Export() + varValue := call.Argument(1).Export() + f.options.GetTemplateCtx(f.input.MetaInput).Set(types.ToString(varName), varValue) + return goja.Null() + }); err != nil { + return err + } + + // iterate provides global iterator function by handling null values or strings + if err := f.jsVM.Set("iterate", func(call goja.FunctionCall) goja.Value { + allVars := []any{} + for _, v := range call.Arguments { + if v.Export() == nil { + continue + } + if v.ExportType().Kind() == reflect.Slice { + // convert []datatype to []interface{} + // since it cannot be type asserted to []interface{} directly + rfValue := reflect.ValueOf(v.Export()) + for i := 0; i < rfValue.Len(); i++ { + allVars = append(allVars, rfValue.Index(i).Interface()) + } + } else { + allVars = append(allVars, v.Export()) + } + } + return f.jsVM.ToValue(allVars) + }); err != nil { + return err + } + + // add a builtin dedupe object + if err := f.jsVM.Set("Dedupe", func(call goja.ConstructorCall) *goja.Object { + d := builtin.NewDedupe(f.jsVM) + obj := call.This + // register these methods + _ = obj.Set("Add", d.Add) + _ = obj.Set("Values", d.Values) + return nil + }); err != nil { + return err + } + + var m = f.options.GetTemplateCtx(f.input.MetaInput).GetAll() + if m == nil { + m = map[string]interface{}{} + } + + if err := f.jsVM.Set("template", m); err != nil { + // all template variables are available in js template object + return err + } + + // register all protocols + for name, fn := range f.protoFunctions { + if err := f.jsVM.Set(name, fn); err != nil { + return err + } + } + + program, err := goja.Compile("flow", f.options.Flow, false) + if err != nil { + return err + } + f.program = program + return nil +} diff --git a/v2/pkg/tmplexec/flow/options.go b/v2/pkg/tmplexec/flow/options.go new file mode 100644 index 00000000..3d845a13 --- /dev/null +++ b/v2/pkg/tmplexec/flow/options.go @@ -0,0 +1,48 @@ +package flow + +import ( + "strings" + + "github.com/projectdiscovery/nuclei/v2/pkg/types" +) + +// ProtoOptions are options that can be passed to flow protocol callback +// ex: dns(protoOptions) <- protoOptions are optional and can be anything +type ProtoOptions struct { + Hide bool + Async bool + protoName string + reqIDS []string +} + +// Examples +// dns() <- callback without any options +// dns(1) or dns(1,3) <- callback with index of protocol in template request at 1 or 1 and 3 +// dns("probe-http") or dns("extract-vpc","probe-http") <- callback with id's instead of index of request in template +// dns({hide:true}) or dns({hide:true,async:true}) <- callback with protocol options +// hide - hides result/event from output & sdk +// async - executes protocols in parallel (implicit wait no need to specify wait) +// Note: all of these options are optional and can be combined together in any order + +// LoadOptions loads the protocol options from a map +func (P *ProtoOptions) LoadOptions(m map[string]interface{}) { + P.Hide = GetBool(m["hide"]) + P.Async = GetBool(m["async"]) +} + +// GetBool returns bool value from interface +func GetBool(value interface{}) bool { + if value == nil { + return false + } + switch v := value.(type) { + case bool: + return v + default: + tmpValue := types.ToString(value) + if strings.EqualFold(tmpValue, "true") { + return true + } + } + return false +} diff --git a/v2/pkg/tmplexec/flow/testcases/condition-flow-extractors.yaml b/v2/pkg/tmplexec/flow/testcases/condition-flow-extractors.yaml new file mode 100644 index 00000000..8dcb7c4f --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/condition-flow-extractors.yaml @@ -0,0 +1,29 @@ +id: ghost-blog-detection +info: + name: Ghost blog detection + author: pdteam + severity: info + + +flow: dns() && http() + +dns: + - name: "{{FQDN}}" + type: CNAME + + extractors: + - type: dsl + name: cname + internal: true + dsl: + - cname + +http: + - method: GET + path: + - "{{BaseURL}}?ref={{cname}}" + + matchers: + - type: word + words: + - "ghost.io" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/testcases/condition-flow-no-operators.yaml b/v2/pkg/tmplexec/flow/testcases/condition-flow-no-operators.yaml new file mode 100644 index 00000000..8cb687b2 --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/condition-flow-no-operators.yaml @@ -0,0 +1,23 @@ +id: ghost-blog-detection +info: + name: Ghost blog detection + author: pdteam + severity: info + + +flow: dns() && http() + + +dns: + - name: "{{FQDN}}" + type: CNAME + +http: + - method: GET + path: + - "{{BaseURL}}?ref={{dns_cname}}" + + matchers: + - type: word + words: + - "ghost.io" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/testcases/condition-flow.yaml b/v2/pkg/tmplexec/flow/testcases/condition-flow.yaml new file mode 100644 index 00000000..d1e2cbf9 --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/condition-flow.yaml @@ -0,0 +1,27 @@ +id: ghost-blog-detection +info: + name: Ghost blog detection + author: pdteam + severity: info + + +flow: dns() && http() + +dns: + - name: "{{FQDN}}" + type: CNAME + + matchers: + - type: word + words: + - "ghost.io" + +http: + - method: GET + path: + - "{{BaseURL}}" + + matchers: + - type: word + words: + - "ghost.io" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-id.yaml b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-id.yaml new file mode 100644 index 00000000..d13121fa --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-id.yaml @@ -0,0 +1,42 @@ +id: nuclei-flow-dns + +info: + name: Nuclei flow dns + author: pdteam + severity: info + description: Description of the Template + reference: https://example-reference-link + +flow: | + dns("fetch-ns"); + template["nameservers"].forEach(nameserver => { + set("nameserver",nameserver); + dns("probe-ns"); + }); + +dns: + - id: "fetch-ns" + name: "{{FQDN}}" + type: NS + matchers: + - type: word + words: + - "IN\tNS" + extractors: + - type: regex + internal: true + name: "nameservers" + group: 1 + regex: + - "IN\tNS\t(.+)" + + - id: "probe-ns" + name: "{{nameserver}}" + type: A + class: inet + retries: 3 + recursion: true + extractors: + - type: dsl + dsl: + - "a" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-prefix.yaml b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-prefix.yaml new file mode 100644 index 00000000..8b5987e3 --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns-prefix.yaml @@ -0,0 +1,41 @@ +id: nuclei-flow-dns + +info: + name: Nuclei flow dns + author: pdteam + severity: info + description: Description of the Template + reference: https://example-reference-link + +flow: | + dns(1); + template["nameservers"].forEach(nameserver => { + set("nameserver",nameserver); + dns("probe-ns"); + }); + +dns: + - name: "{{FQDN}}" + type: NS + matchers: + - type: word + words: + - "IN\tNS" + extractors: + - type: regex + internal: true + name: "nameservers" + group: 1 + regex: + - "IN\tNS\t(.+)" + + - id: "probe-ns" + name: "{{nameserver}}" + type: A + class: inet + retries: 3 + recursion: true + extractors: + - type: dsl + dsl: + - "a" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns.yaml b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns.yaml new file mode 100644 index 00000000..8d5f7906 --- /dev/null +++ b/v2/pkg/tmplexec/flow/testcases/nuclei-flow-dns.yaml @@ -0,0 +1,40 @@ +id: nuclei-flow-dns + +info: + name: Nuclei flow dns + author: pdteam + severity: info + description: Description of the Template + reference: https://example-reference-link + +flow: | + dns(1); + template["nameservers"].forEach(nameserver => { + set("nameserver",nameserver); + dns(2); + }); + +dns: + - name: "{{FQDN}}" + type: NS + matchers: + - type: word + words: + - "IN\tNS" + extractors: + - type: regex + internal: true + name: "nameservers" + group: 1 + regex: + - "IN\tNS\t(.+)" + + - name: "{{nameserver}}" + type: A + class: inet + retries: 3 + recursion: true + extractors: + - type: dsl + dsl: + - "a" \ No newline at end of file diff --git a/v2/pkg/tmplexec/flow/util.go b/v2/pkg/tmplexec/flow/util.go new file mode 100644 index 00000000..6e1c343f --- /dev/null +++ b/v2/pkg/tmplexec/flow/util.go @@ -0,0 +1,23 @@ +package flow + +import "github.com/projectdiscovery/nuclei/v2/pkg/operators" + +// Checks if template has matchers +func hasMatchers(all []*operators.Operators) bool { + for _, operator := range all { + if len(operator.Matchers) > 0 { + return true + } + } + return false +} + +// hasOperators checks if template has operators (i.e matchers/extractors) +func hasOperators(all []*operators.Operators) bool { + for _, operator := range all { + if operator != nil { + return true + } + } + return false +} diff --git a/v2/pkg/tmplexec/generic/exec.go b/v2/pkg/tmplexec/generic/exec.go new file mode 100644 index 00000000..c4c0af75 --- /dev/null +++ b/v2/pkg/tmplexec/generic/exec.go @@ -0,0 +1,94 @@ +package generic + +import ( + "strings" + "sync/atomic" + + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" +) + +// generic engine as name suggests is a generic template +// execution engine and executes all requests one after another +// without any logic in between +type Generic struct { + requests []protocols.Request + options *protocols.ExecutorOptions + results *atomic.Bool +} + +// NewGenericEngine creates a new generic engine from a list of requests +func NewGenericEngine(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *Generic { + if results == nil { + results = &atomic.Bool{} + } + return &Generic{requests: requests, options: options, results: results} +} + +// Compile engine specific compilation +func (g *Generic) Compile() error { + // protocol/ request is already handled by template executer + return nil +} + +// ExecuteWithResults executes the template and returns results +func (g *Generic) ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error { + dynamicValues := make(map[string]interface{}) + if input.HasArgs() { + input.ForEach(func(key string, value interface{}) { + dynamicValues[key] = value + }) + } + previous := make(map[string]interface{}) + + for _, req := range g.requests { + inputItem := input.Clone() + if g.options.InputHelper != nil && input.MetaInput.Input != "" { + if inputItem.MetaInput.Input = g.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" { + return nil + } + } + + err := req.ExecuteWithResults(inputItem, dynamicValues, previous, func(event *output.InternalWrappedEvent) { + if event == nil { + // ideally this should never happen since protocol exits on error and callback is not called + return + } + ID := req.GetID() + if ID != "" { + builder := &strings.Builder{} + for k, v := range event.InternalEvent { + builder.WriteString(ID) + builder.WriteString("_") + builder.WriteString(k) + previous[builder.String()] = v + builder.Reset() + } + } + if event.HasOperatorResult() { + g.results.CompareAndSwap(false, true) + } + // for ExecuteWithResults : this callback will execute user defined callback and some error handling + // for Execute : this callback will print the result to output + callback(event) + }) + if err != nil { + if g.options.HostErrorsCache != nil { + g.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err) + } + gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", g.options.TemplateID, input.MetaInput.PrettyPrint(), err) + } + // If a match was found and stop at first match is set, break out of the loop and return + if g.results.Load() && (g.options.StopAtFirstMatch || g.options.Options.StopAtFirstMatch) { + break + } + } + return nil +} + +// Type returns the type of engine +func (g *Generic) Name() string { + return "generic" +} diff --git a/v2/pkg/tmplexec/interface.go b/v2/pkg/tmplexec/interface.go new file mode 100644 index 00000000..ec52f915 --- /dev/null +++ b/v2/pkg/tmplexec/interface.go @@ -0,0 +1,33 @@ +package tmplexec + +import ( + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/flow" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/generic" + "github.com/projectdiscovery/nuclei/v2/pkg/tmplexec/multiproto" +) + +var ( + _ TemplateEngine = &generic.Generic{} + _ TemplateEngine = &flow.FlowExecutor{} + _ TemplateEngine = &multiproto.MultiProtocol{} +) + +// TemplateEngine is a template executor with different functionality +// Ex: +// 1. generic => executes all protocol requests one after another (Done) +// 2. flow => executes protocol requests based on how they are defined in flow (Done) +// 3. multiprotocol => executes multiple protocols in parallel (Done) +type TemplateEngine interface { + // Note: below methods only need to implement extra / engine specific functionality + // basic request compilation , callbacks , cli output callback etc are handled by `TemplateExecuter` and no need to do it again + // Extra Compilation (if any) + Compile() error + + // ExecuteWithResults executes the template and returns results + ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error + + // Name returns name of template engine + Name() string +} diff --git a/v2/pkg/tmplexec/multiproto/README.md b/v2/pkg/tmplexec/multiproto/README.md new file mode 100644 index 00000000..7f874a8a --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/README.md @@ -0,0 +1,45 @@ +## multi protocol execution + +### Implementation +when template is unmarshalled, if it uses more than one protocol, then order of protocols is preserved and is same is passed to Executor +multiproto is engine/backend for TemplateExecutor which takes care of sharing logic between protocols and executing them in order + +### Execution +when multi protocol template is executed , all protocol requests present in Queue are executed in order +and dynamic values extracted are added to template context. + +- Protocol Responses +apart from extracted `internal:true` values response fields/values of protocol are added to template context at `ExecutorOptions.TemplateCtx` +which takes care of sync and other issues if any. all response fields are prefixed with template type prefix ex: `ssl_subject_dn` + +### Adding New Protocol to multi protocol execution logic +while logic/implementation of multi protocol execution is abstracted. it requires 3 statements to be added in newly implemented protocol +to make response fields of that protocol available to global context + +- Add `request.options.GetTemplateCtx(f.input.MetaInput).GetAll()` to variablesMap in `ExecuteWithResults` Method just above `request.options.Variables.Evaluate` +```go +// example + values := generators.MergeMaps(payloadValues, hostnameVariables, request.options.GetTemplateCtx(f.input.MetaInput).GetAll()) + variablesMap := request.options.Variables.Evaluate(values) +``` + +- Add all response fields to template context just after response map is available +```go + outputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData) + // expose response variables in proto_var format + // this is no-op if the template is not a multi protocol template + request.options.AddTemplateVars(request.Type(),request.ID, outputEvent) +``` + +- Append all available template context values to outputEvent +```go + // add variables from template context before matching/extraction + outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(f.input.MetaInput).GetAll()) +``` + +adding these 3 statements takes care of all logic related to multi protocol execution + +### Exceptions +- statements 1 & 2 are intentionally skipped in `file` protocol to avoid redundant data + - file/dir input paths don't contain variables or are used in path (yet) + - since files are processed by scanning each line. adding statement 2 will unintenionally load all file(s) data diff --git a/v2/pkg/tmplexec/multiproto/doc.go b/v2/pkg/tmplexec/multiproto/doc.go new file mode 100644 index 00000000..a22460da --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/doc.go @@ -0,0 +1,4 @@ +package multiproto + +// multiproto is a template executer engine that executes multiple protocols +// with shared logic in between diff --git a/v2/pkg/tmplexec/multiproto/multi.go b/v2/pkg/tmplexec/multiproto/multi.go new file mode 100644 index 00000000..9a4f37b6 --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/multi.go @@ -0,0 +1,111 @@ +package multiproto + +import ( + "strconv" + "sync/atomic" + + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" +) + +// Mutliprotocol is a template executer engine that executes multiple protocols +// with logic in between +type MultiProtocol struct { + requests []protocols.Request + options *protocols.ExecutorOptions + results *atomic.Bool + readOnlyArgs map[string]interface{} // readOnlyArgs are readonly args that are available after compilation +} + +// NewMultiProtocol creates a new multiprotocol template engine from a list of requests +func NewMultiProtocol(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *MultiProtocol { + if results == nil { + results = &atomic.Bool{} + } + return &MultiProtocol{requests: requests, options: options, results: results} +} + +// Compile engine specific compilation +func (m *MultiProtocol) Compile() error { + // load all variables and evaluate with existing data + variableMap := m.options.Variables.GetAll() + // cli options + optionVars := generators.BuildPayloadFromOptions(m.options.Options) + // constants + constants := m.options.Constants + allVars := generators.MergeMaps(variableMap, constants, optionVars) + allVars = m.options.Variables.Evaluate(allVars) + m.readOnlyArgs = allVars + // no need to load files since they are done at template level + return nil +} + +// ExecuteWithResults executes the template and returns results +func (m *MultiProtocol) ExecuteWithResults(input *contextargs.Context, callback protocols.OutputEventCallback) error { + // put all readonly args into template context + m.options.GetTemplateCtx(input.MetaInput).Merge(m.readOnlyArgs) + var finalProtoEvent *output.InternalWrappedEvent + // callback to process results from all protocols + multiProtoCallback := func(event *output.InternalWrappedEvent) { + if event != nil { + finalProtoEvent = event + } + // export dynamic values from operators (i.e internal:true) + if event.OperatorsResult != nil && len(event.OperatorsResult.DynamicValues) > 0 { + for k, v := range event.OperatorsResult.DynamicValues { + // TBD: iterate-all is only supported in `http` protocol + // we either need to add support for iterate-all in other protocols or implement a different logic (specific to template context) + // currently if dynamic value array only contains one value we replace it with the value + if len(v) == 1 { + m.options.GetTemplateCtx(input.MetaInput).Set(k, v[0]) + } else { + // Note: if extracted value contains multiple values then they can be accessed by indexing + // ex: if values are dynamic = []string{"a","b","c"} then they are available as + // dynamic = "a" , dynamic1 = "b" , dynamic2 = "c" + // we intentionally omit first index for unknown situations (where no of extracted values are not known) + for i, val := range v { + if i == 0 { + m.options.GetTemplateCtx(input.MetaInput).Set(k, val) + } else { + m.options.GetTemplateCtx(input.MetaInput).Set(k+strconv.Itoa(i), val) + } + } + } + } + } + + // evaluate all variables after execution of each protocol + variableMap := m.options.Variables.Evaluate(m.options.GetTemplateCtx(input.MetaInput).GetAll()) + m.options.GetTemplateCtx(input.MetaInput).Merge(variableMap) // merge all variables into template context + } + + // template context: contains values extracted using `internal` extractor from previous protocols + // these values are extracted from each protocol in queue and are passed to next protocol in queue + // instead of adding seperator field to handle such cases these values are appended to `dynamicValues` (which are meant to be used in workflows) + // this makes it possible to use multi protocol templates in workflows + // Note: internal extractor values take precedence over dynamicValues from workflows (i.e other templates in workflow) + + // execute all protocols in the queue + for _, req := range m.requests { + values := m.options.GetTemplateCtx(input.MetaInput).GetAll() + err := req.ExecuteWithResults(input, output.InternalEvent(values), nil, multiProtoCallback) + // if error skip execution of next protocols + if err != nil { + return err + } + } + // Review: how to handle events of multiple protocols in a single template + // currently the outer callback is only executed once (for the last protocol in queue) + // due to workflow logic at https://github.com/projectdiscovery/nuclei/blob/main/v2/pkg/protocols/common/executer/executem.go#L150 + // this causes addition of duplicated / unncessary variables with prefix template_id_all_variables + callback(finalProtoEvent) + + return nil +} + +// Name of the template engine +func (m *MultiProtocol) Name() string { + return "multiproto" +} diff --git a/v2/pkg/tmplexec/multiproto/multi_test.go b/v2/pkg/tmplexec/multiproto/multi_test.go new file mode 100644 index 00000000..907e6ba7 --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/multi_test.go @@ -0,0 +1,73 @@ +package multiproto_test + +import ( + "context" + "log" + "testing" + "time" + + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk" + "github.com/projectdiscovery/nuclei/v2/pkg/parsers" + "github.com/projectdiscovery/nuclei/v2/pkg/progress" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" + "github.com/projectdiscovery/ratelimit" + "github.com/stretchr/testify/require" +) + +var executerOpts protocols.ExecutorOptions + +func setup() { + options := testutils.DefaultOptions + testutils.Init(options) + progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) + + executerOpts = protocols.ExecutorOptions{ + Output: testutils.NewMockOutputWriter(), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + } + workflowLoader, err := parsers.NewLoader(&executerOpts) + if err != nil { + log.Fatalf("Could not create workflow loader: %s\n", err) + } + executerOpts.WorkflowLoader = workflowLoader +} + +func TestMultiProtoWithDynamicExtractor(t *testing.T) { + setup() + Template, err := templates.Parse("testcases/multiprotodynamic.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.Equal(t, 2, len(Template.RequestsQueue)) + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + gotresults, err := Template.Executer.Execute(contextargs.NewWithInput("blog.projectdiscovery.io")) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} + +func TestMultiProtoWithProtoPrefix(t *testing.T) { + setup() + Template, err := templates.Parse("testcases/multiprotowithprefix.yaml", nil, executerOpts) + require.Nil(t, err, "could not parse template") + + require.Equal(t, 3, len(Template.RequestsQueue)) + + err = Template.Executer.Compile() + require.Nil(t, err, "could not compile template") + + gotresults, err := Template.Executer.Execute(contextargs.NewWithInput("blog.projectdiscovery.io")) + require.Nil(t, err, "could not execute template") + require.True(t, gotresults) +} diff --git a/v2/pkg/tmplexec/multiproto/testcases/multiprotodynamic.yaml b/v2/pkg/tmplexec/multiproto/testcases/multiprotodynamic.yaml new file mode 100644 index 00000000..278dc830 --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/testcases/multiprotodynamic.yaml @@ -0,0 +1,30 @@ +id: dns-http-dynamic-values + +info: + name: multi protocol request with dynamic values + author: pdteam + severity: info + +dns: + - name: "{{FQDN}}" # DNS Request + type: cname + + extractors: + - type: dsl + name: blogid + dsl: + - trim_suffix(cname,'.ghost.io.') + internal: true + + +http: + - method: GET # http request + path: + - "{{BaseURL}}" + + matchers: + - type: dsl + dsl: + - contains(body,'ProjectDiscovery.io') # check for http string + - blogid == 'projectdiscovery' # check for cname (extracted information from dns response) + condition: and \ No newline at end of file diff --git a/v2/pkg/tmplexec/multiproto/testcases/multiprotowithprefix.yaml b/v2/pkg/tmplexec/multiproto/testcases/multiprotowithprefix.yaml new file mode 100644 index 00000000..cc161796 --- /dev/null +++ b/v2/pkg/tmplexec/multiproto/testcases/multiprotowithprefix.yaml @@ -0,0 +1,26 @@ +id: dns-http-proto-prefix + +info: + name: multi protocol request with dynamic values + author: pdteam + severity: info + +dns: + - name: "{{FQDN}}" # DNS Request + type: cname + +ssl: + - address: "{{Hostname}}" # ssl request + +http: + - method: GET # http request + path: + - "{{BaseURL}}" + + matchers: + - type: dsl + dsl: + - contains(http_body,'ProjectDiscovery.io') # check for http string + - trim_suffix(dns_cname,'.ghost.io.') == 'projectdiscovery' # check for cname (extracted information from dns response) + - ssl_subject_cn == 'blog.projectdiscovery.io' + condition: and \ No newline at end of file diff --git a/v2/pkg/types/interfaces.go b/v2/pkg/types/interfaces.go index 25dd6856..8b3812bb 100644 --- a/v2/pkg/types/interfaces.go +++ b/v2/pkg/types/interfaces.go @@ -3,6 +3,7 @@ package types import ( + "bytes" "encoding/hex" "fmt" "strconv" @@ -77,6 +78,21 @@ func ToString(data interface{}) string { } } +// ToStringNSlice converts an interface to string in a quick way or to a slice with strings +// if the input is a slice of interfaces. +func ToStringNSlice(data interface{}) interface{} { + switch s := data.(type) { + case []interface{}: + var a []string + for _, v := range s { + a = append(a, ToString(v)) + } + return a + default: + return ToString(data) + } +} + func ToHexOrString(data interface{}) string { switch s := data.(type) { case string: @@ -105,13 +121,32 @@ func ToStringSlice(i interface{}) []string { return v case string: return strings.Fields(v) - case interface{}: - return []string{ToString(v)} default: return nil } } +// ToByteSlice casts an interface to a []byte type. +func ToByteSlice(i interface{}) []byte { + switch v := i.(type) { + case []byte: + return v + case []string: + return []byte(strings.Join(v, "")) + case string: + return []byte(v) + case []interface{}: + var buff bytes.Buffer + for _, u := range v { + buff.WriteString(ToString(u)) + } + return buff.Bytes() + default: + strValue := ToString(i) + return []byte(strValue) + } +} + // ToStringMap casts an interface to a map[string]interface{} type. func ToStringMap(i interface{}) map[string]interface{} { var m = map[string]interface{}{} diff --git a/v2/pkg/types/resume.go b/v2/pkg/types/resume.go index b65a9301..9a19918f 100644 --- a/v2/pkg/types/resume.go +++ b/v2/pkg/types/resume.go @@ -14,7 +14,7 @@ import ( const DefaultResumeFileName = "resume-%s.cfg" func DefaultResumeFilePath() string { - configDir := config.DefaultConfig.GetConfigDir() + configDir := config.DefaultConfig.GetCacheDir() resumeFile := filepath.Join(configDir, fmt.Sprintf(DefaultResumeFileName, xid.New().String())) return resumeFile } diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index 6cc40379..28e003f3 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -2,13 +2,19 @@ package types import ( "io" + "os" + "path/filepath" "strings" "time" "github.com/projectdiscovery/goflags" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" + errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" + folderutil "github.com/projectdiscovery/utils/folder" ) var ( @@ -210,7 +216,7 @@ type Options struct { SystemResolvers bool // ShowActions displays a list of all headless actions ShowActions bool - // Metrics enables display of metrics via an http endpoint + // Deprecated: Enabled by default through clistats . Metrics enables display of metrics via an http endpoint Metrics bool // Debug mode allows debugging request/responses for the engine Debug bool @@ -324,8 +330,6 @@ type Options struct { DisableStdin bool // IncludeConditions is the list of conditions templates should match IncludeConditions goflags.StringSlice - // Custom Config Directory - CustomConfigDir string // Enable uncover engine Uncover bool // Uncover search query @@ -388,6 +392,12 @@ type Options struct { FuzzingMode string // TlsImpersonate enables TLS impersonation TlsImpersonate bool + // CodeTemplateSignaturePublicKey is the custom public key used to verify the template signature (algorithm is automatically inferred from the length) + CodeTemplateSignaturePublicKey string + // CodeTemplateSignatureAlgorithm specifies the sign algorithm (rsa, ecdsa) + CodeTemplateSignatureAlgorithm string + // SignTemplates enables signing of templates + SignTemplates bool } // ShouldLoadResume resume file @@ -460,3 +470,67 @@ func (options *Options) ParseHeadlessOptionalArguments() map[string]string { } return optionalArguments } + +// LoadHelperFile loads a helper file needed for the template +// this respects the sandbox rules and only loads files from +// allowed directories +func (options *Options) LoadHelperFile(helperFile, templatePath string, catalog catalog.Catalog) (io.ReadCloser, error) { + if !options.AllowLocalFileAccess { + // if global file access is disabled try loading with restrictions + absPath, err := options.GetValidAbsPath(helperFile, templatePath) + if err != nil { + return nil, err + } + helperFile = absPath + } + f, err := os.Open(helperFile) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("could not open file %v", helperFile) + } + return f, nil +} + +// GetValidAbsPath returns absolute path of helper file if it is allowed to be loaded +// this respects the sandbox rules and only loads files from allowed directories +func (o *Options) GetValidAbsPath(helperFilePath, templatePath string) (string, error) { + // Conditions to allow helper file + // 1. If helper file is present in nuclei-templates directory + // 2. If helper file and template file are in same directory given that its not root directory + + // resolve and clean helper file path + // ResolveNClean uses a custom base path instead of CWD + resolvedPath, err := fileutil.ResolveNClean(helperFilePath, config.DefaultConfig.GetTemplateDir()) + if err == nil { + // As per rule 1, if helper file is present in nuclei-templates directory, allow it + if strings.HasPrefix(resolvedPath, config.DefaultConfig.GetTemplateDir()) { + return resolvedPath, nil + } + } + + // CleanPath resolves using CWD and cleans the path + helperFilePath, err = fileutil.CleanPath(helperFilePath) + if err != nil { + return "", errorutil.NewWithErr(err).Msgf("could not clean helper file path %v", helperFilePath) + } + + templatePath, err = fileutil.CleanPath(templatePath) + if err != nil { + return "", errorutil.NewWithErr(err).Msgf("could not clean template path %v", templatePath) + } + + // As per rule 2, if template and helper file exist in same directory or helper file existed in any child dir of template dir + // and both of them are present in user home directory, allow it + // Review: should we keep this rule ? add extra option to disable this ? + if isHomeDir(helperFilePath) && isHomeDir(templatePath) && strings.HasPrefix(filepath.Dir(helperFilePath), filepath.Dir(templatePath)) { + return helperFilePath, nil + } + + // all other cases are denied + return "", errorutil.New("access to helper file %v denied", helperFilePath) +} + +// isRootDir checks if given is root directory +func isHomeDir(path string) bool { + homeDir := folderutil.HomeDirOrDefault("") + return strings.HasPrefix(path, homeDir) +} diff --git a/v2/pkg/utils/index.go b/v2/pkg/utils/index.go new file mode 100644 index 00000000..42ad9ba7 --- /dev/null +++ b/v2/pkg/utils/index.go @@ -0,0 +1,16 @@ +package utils + +// TransformIndex transforms user given index (start from 1) to array index (start from 0) +// in safe way without panic i.e negative index or index out of range +func TransformIndex[T any](arr []T, index int) int { + if index <= 1 { + // negative index + return 0 + } + if index >= len(arr) { + // index out of range + return len(arr) - 1 + } + // valid index + return index - 1 +} diff --git a/v2/pkg/utils/insertion_ordered_map.go b/v2/pkg/utils/insertion_ordered_map.go index a9586778..0e3b495d 100644 --- a/v2/pkg/utils/insertion_ordered_map.go +++ b/v2/pkg/utils/insertion_ordered_map.go @@ -58,7 +58,7 @@ func (insertionOrderedStringMap *InsertionOrderedStringMap) UnmarshalJSON(data [ } // toString converts an interface to string in a quick way -func toString(data interface{}) string { +func toString(data interface{}) interface{} { switch s := data.(type) { case nil: return "" @@ -92,6 +92,8 @@ func toString(data interface{}) string { return strconv.FormatUint(uint64(s), 10) case []byte: return string(s) + case []interface{}: + return data default: return fmt.Sprintf("%v", data) } diff --git a/v2/pkg/utils/stats/stats.go b/v2/pkg/utils/stats/stats.go index 9b2b7f2f..a001d989 100644 --- a/v2/pkg/utils/stats/stats.go +++ b/v2/pkg/utils/stats/stats.go @@ -40,6 +40,10 @@ func Display(name string) { Default.Display(name) } +func DisplayAsWarning(name string) { + Default.DisplayAsWarning(name) +} + // GetValue returns the value for a set variable func GetValue(name string) int64 { return Default.GetValue(name) @@ -85,6 +89,21 @@ func (s *Storage) Display(name string) { gologger.Error().Label("WRN").Msgf(data.description, dataValue) } +func (s *Storage) DisplayAsWarning(name string) { + s.mutex.RLock() + data, ok := s.data[name] + s.mutex.RUnlock() + if !ok { + return + } + + dataValue := atomic.LoadInt64(&data.value) + if dataValue == 0 { + return // don't show for nil stats + } + gologger.Warning().Label("WRN").Msgf(data.description, dataValue) +} + // GetValue returns the value for a set variable func (s *Storage) GetValue(name string) int64 { s.mutex.RLock()